Introduction
In this article, I will elucidate how to integrate Stratis Blockchain API in custom or third-party applications. We will create an angular application and integrate Stratis Private APIs. We will interact with Stratis Blockchain APIs and show the response in the custom application. Additionally, this article will describe creating a user interface to deploy the Smart Contract. This write-up explains and provides the code for all endpoints necessary for deploying the Smart Contract; however, you can get ideas and interact with any other endpoints according to your custom software requirement. For more details on how to write Smart Contract and generate byte code, you can visit here.
The source code is available here.
Prerequisites
- IDE for Angular (Visual Studio or Visual Studio Code) as per you
- Stratis FullNode
- Smart Contract Byte Code
For this article, we will be using angular 12, however, it will be the same for another version as well.
Create Angular Application
We will create an angular application with angular CLI as shown below steps,
Install Angular CLI ( if you don’t have it).
npm install -g @angular/cli
We will create a new app with the following command,
ng new StratisFullNodeWebApp
We will navigate to the project folder and run the application. To build and run the angular application we use the serve command as shown.
cd StratisFullNodeWebApp
ng serve
Alternatively, to open the default Angular Application: Run below command which builds, run and open at once.
ng serve --open
The –open command opens a project to the browser at http://localhost:4200/. To design lucrative UI, I have implemented the AdminLTE theme. If you want, you can also implement it following steps from here.
Create and Add Model in Angular App
Create _model folder where we will keep all the models for each component.
We can add the model using the command and also manually as described below: In _model folder right-click–> add a new item –> Add typescript File and rename it according to your structure.
Let’s Add the config.ts file under the model. Where we can keep all the configuration-related information and it will be easy to maintain the configuration-related information from one place. I am creating this to put the root/base URL of API which we call in any services or components. Sample config.ts code,
export const ROOT_URL: string = "http://localhost:38223";
We will integrate the Full Node APIs running at localhost: http://localhost:38223.
I have assumed that you have FullNode already. If you don’t have you can download it from here. Open the project and run the Stratis.CirrusMinerD project in devmode. You can use the below command to run FullNode.
cd StratisFullNode\src\Stratis.CirrusMinerD
dotnet run -devmode=miner
Open http://localhost:38223/swagger/index.html in your browser and you can see a list of APIs.
Wallet Component
To integrate the wallet, we will create a wallet service and wallet component. Run the below command to generate service and components for the wallet.
ng g service _service/stratisfullnode/wallet
ng g component apicollection/wallet
In this article, we will implement 3 endpoints from wallet API, however, the implementations of other API(s) will be similar. From the Wallet API Controller, we will call 3 endpoints: Wallet Load, Wallet balance, and WalletSplitcoins.
Endpoint: /api/Wallet/load: Wallet Load is to make sure the private chain is running smoothly and your app is connected with the chain.
Use the below credential for Wallet Load after the complete design.
Parameters |
Value |
Name |
cirrusdev |
Password |
password |
Endpoint:/api/Wallet/balance: It gives the lists of Wallet addresses, amounts, etc. We need a wallet address having a balance on it to deploy the Smart Contract.
Endpoint: /api/Wallet/splitcoins: It creates the requested amount of equal value and gives the list of addresses with balance. If you get one address having balance and need more addresses, then you can use this to split balance and get more addresses with balance.
To handle API posts and responses easily, it is better to create a model for each of them. Likewise, we will create models for WalletLoad, WalletBalance, and WalletSplitcoins. In order to wallet Load, we have to pass parameters: name and password. Therefore, we will create a model walletload with name and password properties.
Code of walletload.ts model is given below,
export class WalletLoad {
name: string = "";
password: string = "";
}
Similarly, we will create a model for Walletbalance. Code of walletbalance.ts model is,
export class WalletBalance {
walletname: string = "";
accountname: string = "";
includebalancebyaddress: boolean=false;
}
As above, we will create a model For WalletCoinSplit. Code for waletsplitcoin.ts is given below,
export class WalletSplitCoin {
walletName: string = "";
accountName: string = "";
walletPassword: string = "";
totalAmountToSplit: number = 1;
utxosCount: number = 1;
}
Wallet Service
Now, we will create a service class for wallet API(s) as wallet.services.ts. We will import the Root URL from config and use the base URL to call API endpoints. From the Stratis FullNode swagger page, we can see WalletLoad is post method that needs parameters name and password. Similarly, WalletSplitCoin is also a post, and WalletBalance is a get method with parameters. So, we can write code and pass parameters accordingly.
Here, we will create creating three methods in this services as shown,
Below is the code of wallet.services.ts class for those API endpoints.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ROOT_URL } from '../../_model/config';
@Injectable({
providedIn: 'root'
})
export class WalletService {
constructor(private http: HttpClient) { }
LoadWallet(login:any) {
return this.http.post(ROOT_URL + '/api/Wallet/load', login, { observe: 'response' });
}
WalletBalance(params:any) {
return this.http.get(ROOT_URL + '/api/Wallet/balance', { params: params });
}
WalletSplitCoins(walletSplitCoin: any) {
return this.http.post(ROOT_URL + '/api/Wallet/splitcoins', walletSplitCoin);
}
}
We need HttpClient for the http requests so import HttpClient in-app-module.ts as depicted below, if it is not available there, and face an error with it.
Wallet Component
Now we will import Wallet Service, all Wallet-related models, and call methods of service in wallet.component.ts and pass parameters using model respectively.
Code of wallet.component.ts
import { Component, OnInit } from '@angular/core';
import { WalletLoad } from '../../_model/walletload';
import { WalletService } from '../../_service/stratisfullnode/wallet.service';
import { WalletBalance } from '../../_model/walletbalance';
import { WalletSplitCoin } from '../../_model/walletsplitcoins';
import { HttpParams } from '@angular/common/http';
import Swal from 'sweetalert2';
@Component({
selector: 'app-wallet',
templateUrl: './wallet.component.html',
styleUrls: ['./wallet.component.scss']
})
export class WalletComponent implements OnInit {
public login: WalletLoad = new WalletLoad();
public walletbalance: WalletBalance = new WalletBalance();
public walletSplitCoin: WalletSplitCoin = new WalletSplitCoin();
isConnected: boolean = false;
walletInfos: any;
walletbalances: any;
walletSplitedCoins: any;
constructor( private stratisFullNode: WalletService ) { }
IncludeBalanceByAddress: any = ['true', 'false']
ngOnInit(): void {
this.LoadWallet();
}
LoadWallet() {
this.stratisFullNode.LoadWallet(this.login).subscribe((response: any) => {
console.log(response);
if (response.ok) {
this.isConnected = true;
// console.log(response);
Swal.fire('Successful', 'Full Node Connection successful', 'info');
} else {
Swal.fire('Oops...', 'Something went wrong!, Please contact your administrator', 'error');
//alert('Oops...');
(error: any) => {
console.log(error);
}
}
});
}
WalletBalance() {
let params = new HttpParams().set("WalletName", this.walletbalance.walletname).set("AccountName", this.walletbalance.accountname).set("IncludeBalanceByAddress", this.walletbalance.includebalancebyaddress);
this.stratisFullNode.WalletBalance(params).subscribe((response: any) => {
if (response.balances) {
this.walletbalances = response.balances;
//console.log(data);
console.log(this.walletbalances);
} else {
// Swal.fire('Oops...', 'Something went wrong!, Please contact your administrator', 'error');
alert('Opps!!!!');
(error: any) => {
console.log(error);
}
}
});
}
WalletAplitCoins() {
this.stratisFullNode.WalletSplitCoins(this.walletSplitCoin).subscribe((response: any) => {
console.log(response);
if (response.outputs) {
this.walletSplitedCoins = response.outputs;
console.log(this.walletSplitedCoins);
// alert('Load Successfully')
} else {
alert('Oops...');
(error: any) => {
console.log(error);
}
}
});
}
}
For routing, please refer to the app-routing.module.ts from source code. After addition of each component we can add it to this app-routing.module.ts.
Wallet Component Html
In wallet.component.html, we will design forms to pass parameters as user input. It needs one form and submits button for each endpoint. When it gets the response, we are showing the response results just below the submit button. Here is the complete code for wallet.component.html page.
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Stratis FullNode APIs </h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a routerLink="/admin">Wallet</a></li>
<li class="breadcrumb-item active">Load Wallet</li>
</ol>
</div>
</div>
</div><!-- /.container-fluid -->
</section>
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<form (ngSubmit)="walletLoadForm.form.valid && LoadWallet()" #walletLoadForm="ngForm">
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Wallet Load</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" placeholder="Name" [ngClass]="{ 'is-invalid': walletLoadForm.submitted && name.invalid }" [(ngModel)]="login.name" name="name" #name="ngModel" required>
<div class="text-danger" *ngIf="walletLoadForm.submitted && name.invalid">
<p *ngIf="name.errors?.required">Name is required</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Password</label>
<input type="text" class="form-control" placeholder="Password" [(ngModel)]="login.password" name="type" #type="ngModel" [ngClass]="{ 'is-invalid': walletLoadForm.submitted && type.invalid }" required>
<div class="text-danger" *ngIf="walletLoadForm.submitted && type.invalid">
<p *ngIf="type.errors?.required">Password is required</p>
</div>
</div>
</div>
<div class="col-sm-2">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Load Wallet</button>
</div>
<!-- /.col -->
<div class="col-sm-6" *ngIf="isConnected">
<span class="badge badge-success float-sm-right"> Full Node Connection Successful</span>
</div>
</div>
</div>
<!-- /.card-body -->
</div>
</form>
<!--Wallet Balance-->
<form (ngSubmit)="walletBalanceForm.form.valid && WalletBalance()" #walletBalanceForm="ngForm">
<!-- SELECT2 EXAMPLE -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Wallet Balance</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Wallet Name</label>
<input type="text" class="form-control" placeholder="Wallet Name" [ngClass]="{ 'is-invalid': walletBalanceForm.submitted && walletname.invalid }" [(ngModel)]="walletbalance.walletname" name="walletname" #walletname="ngModel" required>
<div class="text-danger" *ngIf="walletBalanceForm.submitted && name.invalid">
<p *ngIf="walletname.errors?.required">Wallet Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Account Name</label>
<input type="text" class="form-control" placeholder="Account Name" [(ngModel)]="walletbalance.accountname" name="accountname" #accountname="ngModel" [ngClass]="{ 'is-invalid': walletBalanceForm.submitted && accountname.invalid }" required>
<div class="text-danger" *ngIf="walletBalanceForm.submitted && accountname.invalid">
<p *ngIf="accountname.errors?.required">Account Name is required</p>
</div>
</div>
</div>
<div class="col-sm-4">
<label for="includebalancebyaddress">IncludeBalanceByAddress</label>
<select class="custom-select" [(ngModel)]="walletbalance.includebalancebyaddress" name="includebalancebyaddress">
<option *ngFor="let includebalance of IncludeBalanceByAddress" [ngValue]="includebalance">{{includebalance}}</option>
</select>
</div>
</div>
<div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Load Wallet Balance</button>
</div>
<!-- /.col -->
</div>
<div *ngIf="walletbalances" class="card card-primary">
<div class="card-body">
<div class="row">
<table id="appsettingtable" class="table table-striped table- table-bordered">
<thead>
<tr>
<th>Account Name</th>
<th>Path</th>
<th>CoinType</th>
<th>AmountConfirmed</th>
<th>Modified by</th>
<th>SpendableAmount </th>
</tr>
</thead>
<tbody>
<tr *ngFor="let balance of walletbalances">
<td>{{balance.accountName}}</td>
<td>{{balance.accountHdPath}}</td>
<td>{{balance.coinType}}</td>
<td>{{balance.amountConfirmed}}</td>
<td>{{balance.amountUnconfirmed}}</td>
<td>
{{balance.spendableAmount}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- /.card-header -->
<h3 class="card-title">List of Addresses</h3>
<div *ngFor="let balance of walletbalances">
<div *ngFor="let add of balance.addresses">
<span><span class="badge">Address:</span> {{add.address}}</span>
<span><span class="badge">IsUsed:</span> {{add.isUsed}}</span>
<span><span class="badge">IsChange:</span> {{add.isChange}}</span>
<span><span class="badge">Amount Confirmed:</span> {{add.amountConfirmed}}</span>
<span><span class="badge">Amount Unconfirmed:</span> {{add.amountUnconfirmed}}</span>
</div>
</div>
</div>
</div>
<!-- /.card-body -->
</div>
</form>
<!--Wallet Split Coins-->
<form (ngSubmit)="walletSplitCoinForm.form.valid && WalletAplitCoins()" #walletSplitCoinForm="ngForm">
<!-- SELECT2 EXAMPLE -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Wallet Split Coins</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Wallet Name</label>
<input type="text" class="form-control" placeholder="Wallet Name" [ngClass]="{ 'is-invalid': walletSplitCoinForm.submitted && walletNameSplitCoin.invalid }" [(ngModel)]="walletSplitCoin.walletName" name="walletName" #walletNameSplitCoin="ngModel" required>
<div class="text-danger" *ngIf="walletSplitCoinForm.submitted && walletNameSplitCoin.invalid">
<p *ngIf="walletNameSplitCoin.errors?.required">Wallet Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Account Name</label>
<input type="text" class="form-control" placeholder="Account Name" [(ngModel)]="walletSplitCoin.accountName" name="accountName" #accountNameSplitCoin="ngModel" [ngClass]="{ 'is-invalid': walletSplitCoinForm.submitted && accountNameSplitCoin.invalid }" required>
<div class="text-danger" *ngIf="walletSplitCoinForm.submitted && accountNameSplitCoin.invalid">
<p *ngIf="accountNameSplitCoin.errors?.required">Account Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Wallet Password</label>
<input type="text" class="form-control" placeholder="Wallet Password" [(ngModel)]="walletSplitCoin.walletPassword" name="walletPassword" #walletpassword="ngModel" [ngClass]="{ 'is-invalid': walletSplitCoinForm.submitted && walletpassword.invalid }" required>
<div class="text-danger" *ngIf="walletSplitCoinForm.submitted && walletpassword.invalid">
<p *ngIf="walletpassword.errors?.required">Wallet Password is required</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Amount to Split</label>
<input type="text" class="form-control" placeholder="No of Aplit" [(ngModel)]="walletSplitCoin.totalAmountToSplit" name="totalAmountToSplit" #totalAmountToSplit="ngModel" [ngClass]="{ 'is-invalid': walletSplitCoinForm.submitted && totalAmountToSplit.invalid }" required>
<div class="text-danger" *ngIf="walletSplitCoinForm.submitted && totalAmountToSplit.invalid">
<p *ngIf="totalAmountToSplit.errors?.required">No of Split is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>No of Split</label>
<input type="text" class="form-control" placeholder="No of Aplit" [(ngModel)]="walletSplitCoin.utxosCount" name="utxosCount" #utxosCount="ngModel" [ngClass]="{ 'is-invalid': walletSplitCoinForm.submitted && utxosCount.invalid }" required>
<div class="text-danger" *ngIf="walletSplitCoinForm.submitted && utxosCount.invalid">
<p *ngIf="utxosCount.errors?.required">No of Split is required</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Wallet Split Coins</button>
</div>
<!-- /.col -->
</div>
</div>
<!-- /.card-body -->
</div>
<div *ngIf="walletSplitedCoins" class="card card-primary">
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<table id="appsettingtable" class="table table-striped table- table-bordered">
<thead>
<tr>
<th>Address</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let splitCoin of walletSplitedCoins">
<td>{{splitCoin.address}}</td>
<td>{{splitCoin.amount}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- /.card-body -->
<div class="card-footer bg-gradient-green">
Stratis Blockchain.
</div>
</div>
<!-- /.card -->
</form>
</div>
</section>
The output of above code of Wallet, Wallet Load.
The output of Wallet Balance,
The output of Wallet Split Coin,
Smart Contract Wallet
Similarly, we will implement two API endpoints from SmartContractWallet: WalletCreate and WalletCall. Endpoint: /api/SmartContractWallet/create is used to deploy the Smart Contract using the Bytecode generated from the Sct tool. If we want to change the state of the contract, API endpoint /api/SmartContractWallet/call can be utilized. You can see business case use of it from the previous article Voting Contract. These endpoints need parameters to pass, So we will create a model for both endpoints.
Create model as SmartContractWalletCreate. Code of smartcontractwalletcreate.ts model is given below,
export class SmartContractWalletCreate {
walletName: string = "";
accountName: string = "";
outpoints: Array<TransactionRequest> = [];
amount: number = 0;
password: string = "";
feeAmount: string = "";
contractCode: string = "";
gasPrice: number = 10000;
gasLimit: number=250000;
sender: string="";
parameters:Array<string>=[];
}
export class TransactionRequest {
index: number = 0;
transactionId: string = "";
}
Code for smartcontractwalletcall.ts,
export class SmartContractWalletCall {
walletName: string = "";
accountName: string = "";
outpoints: Array<TransactionRequest> = [];
contractAddress: string = "";
methodName: string = "";
amount: number = 0;
password: string = "";
feeAmount: string = "";
gasPrice: number = 10000;
gasLimit: number = 250000;
sender: string = "";
parameters: Array<string> = [];
}
export class TransactionRequest {
index: number = 0;
transactionId: string = "";
}
After that, we will create Service and Component for Smart Contract Wallet as shown in angular commands.
ng g service _service/smartcontractwallet --skip-tests
ng g component apicollection/smartcontractwallet --skip-tests
Code for SmartcontractwalletService: smartcontractwallet.service.ts.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ROOT_URL } from '../../_model/config';
@Injectable({
providedIn: 'root'
})
export class SmartcontractwalletService {
constructor(private http: HttpClient) { }
SmartContractWalletCreate(smartContractWalletCreate: any) {
return this.http.post(ROOT_URL + '/api/SmartContractWallet/create', smartContractWalletCreate);
}
SmartContractWalletCall(smartContractWalletCall: any) {
return this.http.post(ROOT_URL + '/api/SmartContractWallet/call', smartContractWalletCall);
}
}
Similarly, we will import Service and model in component and pass parameters to get responses from API endpoints.
Code for smartcontractwallet.component.ts
import { Component, OnInit } from '@angular/core';
import { SmartContractWalletCreate } from '../../_model/smartcontractwalletcreate';
import { SmartcontractwalletService } from '../../_service/stratisfullnode/smartcontractwallet.service';
import { HttpParams } from '@angular/common/http';
import { SmartContractWalletCall } from '../../_model/smartcontractwalletcall';
@Component({
selector: 'app-smartcontractwallet',
templateUrl: './smartcontractwallet.component.html',
styleUrls: ['./smartcontractwallet.component.scss']
})
export class SmartcontractwalletComponent implements OnInit {
public smartContractWalletCreate: SmartContractWalletCreate = new SmartContractWalletCreate();
public smartContractWalletCall: SmartContractWalletCall = new SmartContractWalletCall();
constructor(private smartcontractwalletService: SmartcontractwalletService) { }
contractOutputTrnHash: any;
smartcontractWalletCallOutput: any;
parameter: string = "";
testparam: null;
ngOnInit(): void {
}
SmartContractWalletCreate() {
this.smartContractWalletCreate.outpoints = [];
this.smartContractWalletCreate.parameters[0] = this.parameter;
if (this.parameter==="null") {
this.smartContractWalletCreate.parameters = [];
}
this.smartcontractwalletService.SmartContractWalletCreate(this.smartContractWalletCreate).subscribe((response: any) => {
console.log(response);
if (response) {
this.contractOutputTrnHash = response;
console.log(this.contractOutputTrnHash);
// alert('Load Successfully')
} else {
// Swal.fire('Oops...', 'Something went wrong!, Please contact your administrator', 'error');
alert('Oops...');
(error: any) => {
console.log(error);
}
}
});
}
SmartContractWalletCall() {
this.smartContractWalletCall.outpoints = [];
this.smartContractWalletCall.parameters[0] = this.parameter;
if (this.parameter === "null") {
this.smartContractWalletCall.parameters = [];
}
this.smartcontractwalletService.SmartContractWalletCall(this.smartContractWalletCall).subscribe((response: any) => {
console.log(response);
if (response) {
this.smartcontractWalletCallOutput = response;
console.log(this.smartcontractWalletCallOutput);
// alert('Load Successfully')
} else {
// Swal.fire('Oops...', 'Something went wrong!, Please contact your administrator', 'error');
alert('Oops...');
(error: any) => {
console.log(error);
}
}
});
}
}
HTML page of SmartContractWallet.Component.html page.
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Stratis FullNode APIs </h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<!--<li class="breadcrumb-item"><a routerLink="/admin">Wallet</a></li>
<li class="breadcrumb-item active">Load Wallet</li>-->
</ol>
</div>
</div>
</div><!-- /.container-fluid -->
</section>
<!--SmartContract Wallet Create-->
<form (ngSubmit)="smartContractWalletCreateForm.form.valid && SmartContractWalletCreate()" #smartContractWalletCreateForm="ngForm">
<!-- SELECT2 EXAMPLE -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Smart Contract Wallet Create</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Wallet Name</label>
<input type="text" class="form-control" placeholder="Wallet Name" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && walletName.invalid }" [(ngModel)]="smartContractWalletCreate.walletName" name="walletName" #walletName="ngModel" required>
<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && walletName.invalid">
<p *ngIf="walletName.errors?.required">Wallet Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Account Name</label>
<input type="text" class="form-control" placeholder="Account Name" [(ngModel)]="smartContractWalletCreate.accountName" name="accountName" #accountName="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && accountName.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && accountName.invalid">
<p *ngIf="accountName.errors?.required">Account Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Password</label>
<input type="text" class="form-control" placeholder="Password" [(ngModel)]="smartContractWalletCreate.password" name="password" #password="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && password.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && password.invalid">
<p *ngIf="password.errors?.required">Wallet Password is required</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Amount</label>
<input type="text" class="form-control" placeholder="Amount" [(ngModel)]="smartContractWalletCreate.amount" name="amount" #amount="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && amount.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && amount.invalid">
<p *ngIf="amount.errors?.required">Amount is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Outpoints</label>
<input type="text" class="form-control" placeholder="Outpoints" [(ngModel)]="smartContractWalletCreate.outpoints" name="outpoints" #outpoints="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Fee Amount</label>
<input type="text" class="form-control" placeholder="Fee Amount" [(ngModel)]="smartContractWalletCreate.feeAmount" name="feeAmount" #feeAmount="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Contract Code</label>
<input type="text" class="form-control" placeholder="Contract Code" [(ngModel)]="smartContractWalletCreate.contractCode" name="contractCode" #contractCode="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && contractCode.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && contractCode.invalid">
<p *ngIf="contractCode.errors?.required">Contract Code is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Gas Price</label>
<input type="text" class="form-control" placeholder="Gas Price" [(ngModel)]="smartContractWalletCreate.gasPrice" name="gasPrice" #gasPrice="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Gas Limit</label>
<input type="text" class="form-control" placeholder="Gas Limit" [(ngModel)]="smartContractWalletCreate.gasLimit" name="gasLimit" #gasLimit="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Sender</label>
<input type="text" class="form-control" placeholder="Sender" [(ngModel)]="smartContractWalletCreate.sender" name="sender" #sender="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCreateForm.submitted && sender.invalid }" required>
<!--<div class="text-danger" *ngIf="smartContractWalletCreateForm.submitted && sender.invalid">
<p *ngIf="sender.errors?.required">Contract Code is required</p>
</div>-->
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Parameters</label>
<input type="text" class="form-control" placeholder="Parameters" [(ngModel)]="parameter" name="parameters" #parameters="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Create Wallet</button>
</div>
</div>
<div *ngIf="contractOutputTrnHash">
<span>
<span>Transaction Hash: </span> {{contractOutputTrnHash}}
</span>
</div>
</div>
</div>
</form>
<!--SmartContract Wallet Call-->
<form (ngSubmit)="smartContractWalletCallForm.form.valid && SmartContractWalletCall()" #smartContractWalletCallForm="ngForm">
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Smart Contract Wallet Call</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Wallet Name</label>
<input type="text" class="form-control" placeholder="Wallet Name" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && walletName1.invalid }" [(ngModel)]="smartContractWalletCall.walletName" name="walletName1" #walletName1="ngModel" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && walletName1.invalid">
<p *ngIf="walletName1.errors?.required">Wallet Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Account Name</label>
<input type="text" class="form-control" placeholder="Account Name" [(ngModel)]="smartContractWalletCall.accountName" name="accountName1" #accountName1="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && accountName1.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && accountName1.invalid">
<p *ngIf="accountName1.errors?.required">Account Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Contract Address</label>
<input type="text" class="form-control" placeholder="Contract Address" [(ngModel)]="smartContractWalletCall.contractAddress" name="contractAddress" #contractAddress="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && contractAddress.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && contractAddress.invalid">
<p *ngIf="contractAddress.errors?.required">Contract Address is required</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Password</label>
<input type="text" class="form-control" placeholder="Password" [(ngModel)]="smartContractWalletCall.password" name="password1" #password1="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && password1.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && password1.invalid">
<p *ngIf="password1.errors?.required">Wallet Password is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Amount</label>
<input type="text" class="form-control" placeholder="Amount" [(ngModel)]="smartContractWalletCall.amount" name="amount" #amount="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && amount.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && amount.invalid">
<p *ngIf="amount.errors?.required">Amount is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Outpoints</label>
<input type="text" class="form-control" placeholder="Outpoints" [(ngModel)]="smartContractWalletCall.outpoints" name="outpoints" #outpoints="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Fee Amount</label>
<input type="text" class="form-control" placeholder="Fee Amount" [(ngModel)]="smartContractWalletCall.feeAmount" name="feeAmount" #feeAmount="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Gas Price</label>
<input type="text" class="form-control" placeholder="Gas Price" [(ngModel)]="smartContractWalletCall.gasPrice" name="gasPrice" #gasPrice="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Gas Limit</label>
<input type="text" class="form-control" placeholder="Gas Limit" [(ngModel)]="smartContractWalletCall.gasLimit" name="gasLimit" #gasLimit="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Sender</label>
<input type="text" class="form-control" placeholder="Sender" [(ngModel)]="smartContractWalletCall.sender" name="sender1" #sender1="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && sender1.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && sender1.invalid">
<p *ngIf="sender1.errors?.required">Sender is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Method Name</label>
<input type="text" class="form-control" placeholder="Method Name" [(ngModel)]="smartContractWalletCall.methodName" name="methodName" #methodName="ngModel" [ngClass]="{ 'is-invalid': smartContractWalletCallForm.submitted && methodName.invalid }" required>
<div class="text-danger" *ngIf="smartContractWalletCallForm.submitted && methodName.invalid">
<p *ngIf="methodName.errors?.required">Method Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Parameters</label>
<input type="text" class="form-control" placeholder="Parameters" [(ngModel)]="parameter" name="parameters" #parameters="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Wallet Call</button>
</div>
<!-- /.col -->
</div>
<div *ngIf="smartcontractWalletCallOutput">
<h3 class="card-title">Response output</h3><br />
<span><span class="badge">Fee :</span> {{smartcontractWalletCallOutput.fee}}</span><br />
<span><span class="badge">Hex:</span> {{smartcontractWalletCallOutput.hex}}</span><br />
<span><span class="badge">Message:</span> {{smartcontractWalletCallOutput.message}}</span><br />
<span><span class="badge">Success:</span> {{smartcontractWalletCallOutput.success}}</span><br />
<span><span class="Transaction Id">To:</span> {{smartcontractWalletCallOutput.transactionId}}</span><br />
</div>
</div>
<!-- /.card-body -->
<div class="card-footer bg-gradient-green">
Stratis Blockchain.
</div>
</div>
</form>
The output of above Smart Contract Wallet Creates page.
Output UI to interact with Amart Contract Wallet Call (endpoint: /api/SmartContractWallet/call).
Smart Contract Component
From the SmartContract API Controller, we will call endpoint API/SmartContracts/receipt which gives the receipt of the transaction. We have to pass transaction hash to get transaction receipt. It gives transaction receipts including Gas Used, From and To address, Logs, etc. Additionally, we will integrate endpoint /api/SmartContracts/local-call which creates a local call with a contract without a transaction. As we saw in the previous article when to use local call and contract call.
We will create a Model for GetReceipt and Local-Call.
Model code for smartcontractreceipt.ts
export class SmartContractReceipt {
txHash: string = "";
}
Model code for smartcontractlocalcall.ts class.
export class SmartContractLocalCall {
contractAddress: string = "";
methodName: string = "";
amount: string = "";
gasPrice: number = 10000;
gasLimit: number = 250000;
sender: string = "";
parameters: Array<string> = [];
}
Generate Component and Service for Smart Contract API Controller.
ng g service _service/smartcontract --skip-tests
ng g component apicollection/smartcontract --skip-tests
Code of SmartContractService class,
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ROOT_URL } from '../../_model/config';
@Injectable({
providedIn: 'root'
})
export class SmartcontractService {
constructor(private http: HttpClient) { }
SmartContractReceipt(params: any) {
return this.http.get(ROOT_URL + '/api/SmartContracts/receipt', { params: params });
}
SmartContractLocalCall(smartContractLocalCall: any) {
return this.http.post(ROOT_URL + '/api/SmartContracts/local-call', smartContractLocalCall);
}
}
Code of smartcontract.component.ts,
import { Component, OnInit } from '@angular/core';
import { SmartContractReceipt } from '../../_model/smartcontractreceipt';
import { SmartContractLocalCall } from '../../_model/smartcontractlocalcall';
import { SmartcontractService } from '../../_service/stratisfullnode/smartcontract.service';
import { HttpParams } from '@angular/common/http';
@Component({
selector: 'app-smartcontract',
templateUrl: './smartcontract.component.html',
styleUrls: ['./smartcontract.component.scss']
})
export class SmartcontractComponent implements OnInit {
public smartContractReceipt: SmartContractReceipt = new SmartContractReceipt();
public smartContractLocalCall: SmartContractLocalCall = new SmartContractLocalCall();
constructor(private smartcontractService: SmartcontractService) { }
resultGetReceipt: any;
parameter: string = "";
localCallResponseOut: any;
ngOnInit(): void {
}
SmartContractReceipt() {
let params = new HttpParams().set("txHash", this.smartContractReceipt.txHash);
this.smartcontractService.SmartContractReceipt(params).subscribe((response: any) => {
if (response) {
this.resultGetReceipt = response;
console.log(this.resultGetReceipt);
} else {
alert('Opps!!!!');
(error: any) => {
console.log(error);
}
}
});
}
SmartContractForLocalCall() {
this.smartContractLocalCall.parameters= []; this.smartcontractService.SmartContractLocalCall(this.smartContractLocalCall).subscribe((response: any) => {
console.log(response);
if (response) {
this.localCallResponseOut = response;
console.log(this.localCallResponseOut);
} else {
alert('Oops...');
(error: any) => {
console.log(error);
}
}
});
}
}
HTML for smartcontract.component.html,
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Stratis FullNode APIs </h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
</ol>
</div>
</div>
</div><!-- /.container-fluid -->
</section>
<!--Get Receipt-->
<form (ngSubmit)="smartcontractGetReceiptForm.form.valid && SmartContractReceipt()" #smartcontractGetReceiptForm="ngForm">
<!-- SELECT2 EXAMPLE -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Smart Contract Receipt</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label>Transaction Hash </label>
<input type="text" class="form-control" placeholder="Transaction Hash" [ngClass]="{ 'is-invalid': smartcontractGetReceiptForm.submitted && txHash.invalid }" [(ngModel)]="smartContractReceipt.txHash" name="txHash" #txHash="ngModel" required>
<div class="text-danger" *ngIf="smartcontractGetReceiptForm.submitted && txHash.invalid">
<p *ngIf="txHash.errors?.required">Transaction Hash is required</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Get Receipt</button>
</div>
<!-- /.col -->
</div>
<!-- /.card-body -->
<div *ngIf="resultGetReceipt">
<h3 class="card-title">Response output</h3><br />
<span><span class="badge">Transaction Hash:</span> {{resultGetReceipt.transactionHash}}</span><br />
<span><span class="badge">Block Hash:</span> {{resultGetReceipt.blockHash}}</span><br />
<span><span class="badge">PostState:</span> {{resultGetReceipt.postState}}</span><br />
<span><span class="badge">From:</span> {{resultGetReceipt.from}}</span><br />
<span><span class="badge">To:</span> {{resultGetReceipt.to}}</span><br />
<span><span class="badge">GasUsed:</span> {{resultGetReceipt.gasUsed}}</span><br />
<span><span class="badge">New Contract Address:</span> {{resultGetReceipt.newContractAddress}}</span><br />
<span><span class="badge">Success:</span> {{resultGetReceipt.success}}</span><br />
<span><span class="badge">Return Value:</span> {{resultGetReceipt.returnValue}}</span><br />
<span><span class="badge">Bloom:</span> {{resultGetReceipt.bloom}}</span><br />
<span><span class="badge">Error:</span> {{resultGetReceipt.error}}</span><br />
<span><span class="badge">Log:</span> {{resultGetReceipt.logs}}</span><br />
</div>
</div>
</div>
</form>
<form (ngSubmit)="smartcontractLocalCallForm.form.valid && SmartContractForLocalCall()" #smartcontractLocalCallForm="ngForm">
<!-- SELECT2 EXAMPLE -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">Smart Contract Local Call</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Contract Address </label>
<input type="text" class="form-control" placeholder="Contract Address" [ngClass]="{ 'is-invalid': smartcontractLocalCallForm.submitted && contractAddress.invalid }" [(ngModel)]="smartContractLocalCall.contractAddress" name="contractAddress" #contractAddress="ngModel" required>
<div class="text-danger" *ngIf="smartcontractLocalCallForm.submitted && contractAddress.invalid">
<p *ngIf="contractAddress.errors?.required">Contract Address is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Method Name </label>
<input type="text" class="form-control" placeholder="Method Name" [ngClass]="{ 'is-invalid': smartcontractLocalCallForm.submitted && methodName.invalid }" [(ngModel)]="smartContractLocalCall.methodName" name="methodName" #methodName="ngModel" required>
<div class="text-danger" *ngIf="smartcontractLocalCallForm.submitted && methodName.invalid">
<p *ngIf="methodName.errors?.required">Method Name is required</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Amount</label>
<input type="text" class="form-control" placeholder="Amount" [(ngModel)]="smartContractLocalCall.amount" name="amount" #amount="ngModel" [ngClass]="{ 'is-invalid': smartcontractLocalCallForm.submitted && amount.invalid }" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Gas Price</label>
<input type="text" class="form-control" placeholder="Gas Price" [(ngModel)]="smartContractLocalCall.gasPrice" name="gasPrice" #gasPrice="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Gas Limit</label>
<input type="text" class="form-control" placeholder="Gas Limit" [(ngModel)]="smartContractLocalCall.gasLimit" name="gasLimit" #gasLimit="ngModel">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Sender</label>
<input type="text" class="form-control" placeholder="Sender" [(ngModel)]="smartContractLocalCall.sender" name="sender" #sender="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Parameters</label>
<input type="text" class="form-control" placeholder="Parameters" [(ngModel)]="parameter" name="parameters" #parameters="ngModel">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" class="btn btn-primary"><i class="fa fa-lock"></i> Make Local Call</button>
</div>
</div>
<!-- /.card-body -->
<div *ngIf="localCallResponseOut">
<h3 class="card-title">Response output</h3><br />
<span><span class="badge">Internal Transfer:</span> {{localCallResponseOut.internalTransfers}}</span><br />
<span><span class="badge">GasConsumed Value:</span> {{localCallResponseOut.gasConsumed.value}}</span><br />
<span><span class="badge">Revert:</span> {{localCallResponseOut.revert}}</span><br />
<span><span class="badge">Error Message:</span> {{localCallResponseOut.errorMessage}}</span><br />
<span><span class="badge">Return:</span> {{localCallResponseOut.return}}</span><br />
<span><span class="badge">Log:</span> {{localCallResponseOut.logs}}</span><br />
</div>
</div>
</div>
<div class="card-footer bg-gradient-green">
Stratis Blockchain.
</div>
<!-- /.card -->
</form>
Output of Smart Contract Get Receipt UI.
Smart Contract Local call UI.
Summary
Hence, we can integrate Stratis API in any custom application. In this article, we have implemented Stratis private blockchain in our own application through which we can deploy the Smart Contract. Additionally, write-up has shown how can you manage the API get, post and response model as well as segregate services, models for APIs. The article has explained the complete steps to develop components with user interface to validate Startis API with input and response formatted output. Furthermore, you can take idea from this and integrate Stratis Blockchain any API endpoint to your custom application based on your application need. I have developed an angular application for complete demonstration which you can get from repository.