2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/RpcEntities/EndorsingRightsRpcEntity.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AgileVentures.TezPusher.Model.Interfaces;
3 |
4 | namespace AgileVentures.TezPusher.Model.RpcEntities
5 | {
6 | public class EndorsingRightsRpcEntity : IRpcEntity
7 | {
8 | public List Rights { get; set; }
9 | }
10 |
11 | public class EndorsingRight
12 | {
13 | public long level { get; set; }
14 | public string @delegate { get; set; }
15 | public List slots { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/docs-welcome.md:
--------------------------------------------------------------------------------
1 | # Welcome
2 |
3 | Welcome to the technical documentation site for the TezosLive.io project!
4 |
5 | In this documentation you'll find information on:
6 |
7 | * [Tutorials ](docs-getting-started/docs-using-tezoslive.io-endpoint.md)to get you started with TaaS \(Tezos as a Service\)
8 | * An [overview ](./#how-to-use)of the different options you have when running TaaS
9 | * [Sample clients](docs-sample-clients/docs-agileventures.tezpusher.sampleclient.md) for different usage options
10 |
11 | ### About TaaS
12 |
13 | TaaS provides real-time updates to various applications based on the events happening on Tezos by leveraging SignalR \(WebSocket\).
14 |
15 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/RpcEntities/ContractRpcEntity.cs:
--------------------------------------------------------------------------------
1 | using AgileVentures.TezPusher.Model.Interfaces;
2 |
3 | namespace AgileVentures.TezPusher.Model.RpcEntities
4 | {
5 | public class Delegate
6 | {
7 | public bool setable { get; set; }
8 | }
9 |
10 | public class ContractRpcEntity : IRpcEntity
11 | {
12 | public string manager { get; set; }
13 | public string balance { get; set; }
14 | public bool spendable { get; set; }
15 | public Delegate @delegate { get; set; }
16 | public int counter { get; set; }
17 | }
18 |
19 | public class ContractBalanceRpcEntity : IRpcEntity
20 | {
21 | public string balance { get; set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # System Files
38 | .DS_Store
39 | Thumbs.db
40 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/RpcEntities/MonitorHeadModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace AgileVentures.TezPusher.Model.RpcEntities
5 | {
6 | public class MonitorHeadModel
7 | {
8 | public string hash { get; set; }
9 | public long level { get; set; }
10 | public long proto { get; set; }
11 | public string predecessor { get; set; }
12 | public DateTime timestamp { get; set; }
13 | public long validation_pass { get; set; }
14 | public string operations_hash { get; set; }
15 | public List fitness { get; set; }
16 | public string context { get; set; }
17 | public string protocol_data { get; set; }
18 | }
19 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/app/pipes.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | export class TezUtils {
4 | public static round(value, precision): number {
5 | const multiplier = Math.pow(10, precision || 0);
6 | return Math.round(value * multiplier) / multiplier;
7 | }
8 | }
9 |
10 | @Pipe({ name: 'amountToTez' })
11 | export class AmountToTezPipe implements PipeTransform {
12 | transform(value: number): number {
13 | return TezUtils.round(value / 1000000, 3);
14 | }
15 | }
16 |
17 | @Pipe({ name: 'feeToTez' })
18 | export class FeeToTezPipe implements PipeTransform {
19 | transform(value: number): number {
20 | return TezUtils.round(value / 1000000, 6);
21 | }
22 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # System Files
38 | .DS_Store
39 | Thumbs.db
40 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/app/pipes.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | export class TezUtils {
4 | public static round(value, precision): number {
5 | const multiplier = Math.pow(10, precision || 0);
6 | return Math.round(value * multiplier) / multiplier;
7 | }
8 | }
9 |
10 | @Pipe({ name: 'amountToTez' })
11 | export class AmountToTezPipe implements PipeTransform {
12 | transform(value: number): number {
13 | return TezUtils.round(value / 1000000, 3);
14 | }
15 | }
16 |
17 | @Pipe({ name: 'feeToTez' })
18 | export class FeeToTezPipe implements PipeTransform {
19 | transform(value: number): number {
20 | return TezUtils.round(value / 1000000, 6);
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Function/NegotiateFunction.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using Microsoft.Azure.WebJobs;
3 | using Microsoft.Azure.WebJobs.Extensions.Http;
4 | using Microsoft.Azure.WebJobs.Extensions.SignalRService;
5 |
6 | namespace AgileVentures.TezPusher.Function
7 | {
8 | public static class NegotiateFunction
9 | {
10 | [FunctionName("negotiate")]
11 | public static SignalRConnectionInfo Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")]HttpRequest req,
12 | [SignalRConnectionInfo(HubName = "broadcast", UserId = "{headers.x-tezos-live-userid}")] SignalRConnectionInfo connectionInfo,
13 | Microsoft.Extensions.Logging.ILogger log)
14 | {
15 | return connectionInfo;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Service/AgileVentures.TezPusher.Service.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/Constants/TezosBlockOperationConstants.cs:
--------------------------------------------------------------------------------
1 | namespace AgileVentures.TezPusher.Model.Constants
2 | {
3 | public static class TezosBlockOperationConstants
4 | {
5 | public const string Endorsement = "endorsement";
6 | public const string Transaction = "transaction";
7 | public const string Origination = "origination";
8 | public const string Delegation = "delegation";
9 | public const string OperationResultStatusApplied = "applied";
10 | public const string BalanceUpdateKindContract = "contract";
11 | public const string BalanceUpdateKindFreezer = "freezer";
12 | public const string BalanceUpdateCategoryDeposits = "deposits";
13 | public const string BalanceUpdateCategoryRewards = "rewards";
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base
2 | WORKDIR /app
3 | EXPOSE 80
4 | EXPOSE 443
5 |
6 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
7 | WORKDIR /src
8 | COPY ["AgileVentures.TezPusher.Web/AgileVentures.TezPusher.Web.csproj", "AgileVentures.TezPusher.Web/"]
9 | RUN dotnet restore "AgileVentures.TezPusher.Web/AgileVentures.TezPusher.Web.csproj"
10 | COPY . .
11 | WORKDIR "/src/AgileVentures.TezPusher.Web"
12 | RUN dotnet build "AgileVentures.TezPusher.Web.csproj" -c Release -o /app/build
13 |
14 | FROM build AS publish
15 | RUN dotnet publish "AgileVentures.TezPusher.Web.csproj" -c Release -o /app/publish
16 |
17 | FROM base AS final
18 | WORKDIR /app
19 | COPY --from=publish /app/publish .
20 | ENTRYPOINT ["dotnet", "AgileVentures.TezPusher.Web.dll"]
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/AgileVentures.TezPusher.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | InProcess
6 | 6502073c-ca48-4458-8c72-045a19b894d9
7 | Linux
8 | 8.0
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.ConsoleApp/Dockerfile:
--------------------------------------------------------------------------------
1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
2 |
3 | FROM mcr.microsoft.com/dotnet/core/runtime:2.2-stretch-slim AS base
4 | WORKDIR /app
5 |
6 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
7 | WORKDIR /src
8 | COPY ["AgileVentures.TezPusher.ConsoleApp/AgileVentures.TezPusher.ConsoleApp.csproj", "AgileVentures.TezPusher.ConsoleApp/"]
9 | COPY ["AgileVentures.TezPusher.Model/AgileVentures.TezPusher.Model.csproj", "AgileVentures.TezPusher.Model/"]
10 | RUN dotnet restore "AgileVentures.TezPusher.ConsoleApp/AgileVentures.TezPusher.ConsoleApp.csproj"
11 | COPY . .
12 | WORKDIR "/src/AgileVentures.TezPusher.ConsoleApp"
13 | RUN dotnet build "AgileVentures.TezPusher.ConsoleApp.csproj" -c Release -o /app/build
14 |
15 | FROM build AS publish
16 | RUN dotnet publish "AgileVentures.TezPusher.ConsoleApp.csproj" -c Release -o /app/publish
17 |
18 | FROM base AS final
19 | WORKDIR /app
20 | COPY --from=publish /app/publish .
21 | ENTRYPOINT ["dotnet", "AgileVentures.TezPusher.ConsoleApp.dll"]
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/app/serviceurl-dialog/serviceurl-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
2 | import { Component, OnInit, Inject } from '@angular/core';
3 | import { FormGroup, FormBuilder, Validators } from '@angular/forms';
4 |
5 | @Component({
6 | selector: 'app-serviceurl-dialog',
7 | templateUrl: './serviceurl-dialog.component.html'
8 | })
9 |
10 | export class ServiceUrlDialogComponent implements OnInit {
11 |
12 | form: FormGroup;
13 | endpoint: string;
14 |
15 | constructor(
16 | private fb: FormBuilder,
17 | private dialogRef: MatDialogRef,
18 | @Inject(MAT_DIALOG_DATA) { endpoint }: EndpointConfig) {
19 | this.endpoint = endpoint;
20 | this.form = fb.group({
21 | endpoint: [endpoint, Validators.required],
22 | });
23 | }
24 |
25 | ngOnInit() { }
26 |
27 | close() {
28 | this.dialogRef.close(this.form.value.endpoint);
29 | }
30 | }
31 |
32 | export interface EndpointConfig {
33 | endpoint: string;
34 | }
35 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.ConsoleApp/nlog.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 agile-ventures
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/HttpClients/TezosMonitorClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Net.Http.Headers;
4 | using AgileVentures.TezPusher.Web.Configurations;
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Extensions.Options;
7 |
8 | namespace AgileVentures.TezPusher.Web.HttpClients
9 | {
10 | public class TezosMonitorClient
11 | {
12 | public HttpClient Client { get; }
13 |
14 | public TezosMonitorClient(HttpClient client, IOptions tezosConfig, ILogger logger)
15 | {
16 | try
17 | {
18 | Client = client;
19 | Client.BaseAddress = new Uri(tezosConfig.Value.NodeUrl);
20 | Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
21 | }
22 | catch (OptionsValidationException ex)
23 | {
24 | foreach (var failure in ex.Failures)
25 | {
26 | logger.LogCritical(failure);
27 | throw;
28 | }
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/docs-getting-started/docs-using-tezoslive.io-endpoint.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: 'Most convenient option, no server side infrastructure needed.'
3 | ---
4 |
5 | # Using TezosLive.io Endpoint
6 |
7 | ## Getting your TaaS Endpoint at TezosLive.io
8 |
9 | {% hint style="danger" %}
10 | **Requirements**
11 |
12 | * GitHub account
13 | {% endhint %}
14 |
15 | Sign in using your GitHub account on [TezosLive.io](https://www.tezoslive.io) and request your endpoint.
16 | You don't need to setup or host any server-side or Tezos infrastructure on your side.
17 |
18 | You can also check the [Medium article ](https://medium.com/tezoslive/public-tezos-signalr-websocket-endpoint-available-on-tezoslive-io-28e0dcfcc8f)about the public endpoints.
19 |
20 | {% hint style="warning" %}
21 | **Limitations**
22 |
23 | Public API endpoints are currently limited to
24 |
25 | * 20 000 messages per account per day \(1 message is counted for each 64kB in case message has more than 64kB\)
26 | * 20 concurrent connection per account
27 | {% endhint %}
28 |
29 | **For client side instructions** please see
30 |
31 | {% page-ref page="../docs-clients/docs-taas-endpoint-or-function.md" %}
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/AgileVentures.TezPusher.Pusher/bin/Debug/netcoreapp2.2/AgileVentures.TezPusher.Pusher.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Pusher",
15 | "console": "internalConsole",
16 | "stopAtEntry": false
17 | },
18 | {
19 | "name": ".NET Core Attach",
20 | "type": "coreclr",
21 | "request": "attach",
22 | "processId": "${command:pickProcess}"
23 | },
24 | {
25 | "name": "Attach to .NET Functions",
26 | "type": "coreclr",
27 | "request": "attach",
28 | "processId": "${command:azureFunctions.pickProcess}"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/HeadModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using AgileVentures.TezPusher.Model.RpcEntities;
4 |
5 | namespace AgileVentures.TezPusher.Model.PushEntities
6 | {
7 | public class HeadModel : Header
8 | {
9 | public HeadModel(BlockRpcEntity model)
10 | {
11 | protocol = model.protocol;
12 | chain_id = model.chain_id;
13 | hash = model.hash;
14 | level = model.header.level;
15 | proto = model.header.proto;
16 | predecessor = model.header.predecessor;
17 | timestamp = model.header.timestamp;
18 | validation_pass = model.header.validation_pass;
19 | operations_hash = model.header.operations_hash;
20 | fitness = model.header.fitness;
21 | context = model.header.context;
22 | proof_of_work_nonce = model.header.proof_of_work_nonce;
23 | signature = model.header.signature;
24 | }
25 |
26 | public string protocol { get; set; }
27 | public string chain_id { get; set; }
28 | public string hash { get; set; }
29 | }
30 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Function/AgileVentures.TezPusher.Function.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | v2
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 | PreserveNewest
20 |
21 |
22 | PreserveNewest
23 | Never
24 |
25 |
26 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TezPusherSampleClientWeb",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build"
8 | },
9 | "private": true,
10 | "dependencies": {
11 | "@angular/animations": "^8.2.3",
12 | "@angular/cdk": "^8.2.3",
13 | "@angular/common": "^8.2.3",
14 | "@angular/compiler": "^8.2.3",
15 | "@angular/core": "^8.2.3",
16 | "@angular/forms": "^8.2.3",
17 | "@angular/material": "^8.2.3",
18 | "@angular/platform-browser": "^8.2.3",
19 | "@angular/platform-browser-dynamic": "^8.2.3",
20 | "@angular/router": "^8.2.3",
21 | "@aspnet/signalr": "^1.1.4",
22 | "core-js": "^2.5.4",
23 | "rxjs": "6.4.0",
24 | "zone.js": "^0.9.1"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "^0.803.26",
28 | "@angular/cli": "~8.3.24",
29 | "@angular/compiler-cli": "^8.2.14",
30 | "@angular/language-service": "^8.2.14",
31 | "@types/node": "~8.9.4",
32 | "codelyzer": "~5.2.1",
33 | "hammerjs": "^2.0.8",
34 | "ts-node": "~5.0.1",
35 | "tslint": "~5.9.1",
36 | "typescript": "~3.5.3",
37 | "uuid": "^3.3.3"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TezPusherSampleClient",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build"
8 | },
9 | "private": true,
10 | "dependencies": {
11 | "@angular/animations": "^8.2.3",
12 | "@angular/cdk": "^8.2.3",
13 | "@angular/common": "^8.2.3",
14 | "@angular/compiler": "^8.2.3",
15 | "@angular/core": "^8.2.3",
16 | "@angular/forms": "^8.2.3",
17 | "@angular/material": "^8.2.3",
18 | "@angular/platform-browser": "^8.2.3",
19 | "@angular/platform-browser-dynamic": "^8.2.3",
20 | "@angular/router": "^8.2.3",
21 | "@aspnet/signalr": "^1.1.4",
22 | "core-js": "^2.5.4",
23 | "rxjs": "6.4.0",
24 | "zone.js": "^0.9.1"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "^0.803.26",
28 | "@angular/cli": "~8.3.24",
29 | "@angular/compiler-cli": "^8.2.14",
30 | "@angular/language-service": "^8.2.14",
31 | "@types/node": "~8.9.4",
32 | "codelyzer": "~5.2.1",
33 | "hammerjs": "^2.0.8",
34 | "ts-node": "~5.0.1",
35 | "tslint": "~5.9.1",
36 | "typescript": "~3.5.3",
37 | "uuid": "^3.3.3"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/SubscribeModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 |
4 | namespace AgileVentures.TezPusher.Model.PushEntities
5 | {
6 | public class SubscribeModel
7 | {
8 | [JsonProperty(PropertyName = "userId", NullValueHandling = NullValueHandling.Ignore)]
9 | public string UserId { get; set; }
10 |
11 | [JsonProperty(PropertyName = "transactionAddresses", NullValueHandling = NullValueHandling.Ignore)]
12 | public List TransactionAddresses { get; set; }
13 |
14 | [JsonProperty(PropertyName = "originationAddresses", NullValueHandling = NullValueHandling.Ignore)]
15 | public List OriginationAddresses { get; set; }
16 |
17 | [JsonProperty(PropertyName = "delegationAddresses", NullValueHandling = NullValueHandling.Ignore)]
18 | public List DelegationAddresses { get; set; }
19 |
20 | [JsonProperty(PropertyName = "fromBlockLevel", NullValueHandling = NullValueHandling.Ignore)]
21 | public ulong? FromBlockLevel { get; set; }
22 |
23 | [JsonProperty(PropertyName = "blockHeaders", NullValueHandling = NullValueHandling.Ignore)]
24 | public bool BlockHeaders { get; set; }
25 | }
26 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/DelegationModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AgileVentures.TezPusher.Model.RpcEntities;
3 | using Newtonsoft.Json;
4 |
5 | namespace AgileVentures.TezPusher.Model.PushEntities
6 | {
7 | public class DelegationModel
8 | {
9 | public DelegationModel(BlockRpcEntity block, BlockOperation transactionOperation, BlockDelegationContent delegationContent)
10 | {
11 | BlockHash = block.hash;
12 | BlockLevel = block.header.level;
13 | OperationHash = transactionOperation.hash;
14 | Timestamp = block.header.timestamp;
15 | DelegationContent = delegationContent;
16 | }
17 |
18 | [JsonProperty(PropertyName = "block_hash")]
19 | public string BlockHash { get; set; }
20 |
21 | [JsonProperty(PropertyName = "block_level")]
22 | public long BlockLevel { get; set; }
23 |
24 | [JsonProperty(PropertyName = "operation_hash")]
25 | public string OperationHash { get; set; }
26 |
27 | [JsonProperty(PropertyName = "timestamp")]
28 | public DateTime Timestamp { get; set; }
29 |
30 | [JsonProperty(PropertyName = "delegation_content")]
31 | public BlockDelegationContent DelegationContent { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/OriginationModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AgileVentures.TezPusher.Model.RpcEntities;
3 | using Newtonsoft.Json;
4 |
5 | namespace AgileVentures.TezPusher.Model.PushEntities
6 | {
7 | public class OriginationModel
8 | {
9 | public OriginationModel(BlockRpcEntity block, BlockOperation transactionOperation, BlockOriginationContent originationContent)
10 | {
11 | BlockHash = block.hash;
12 | BlockLevel = block.header.level;
13 | OperationHash = transactionOperation.hash;
14 | Timestamp = block.header.timestamp;
15 | OriginationContent = originationContent;
16 | }
17 |
18 | [JsonProperty(PropertyName = "block_hash")]
19 | public string BlockHash { get; set; }
20 |
21 | [JsonProperty(PropertyName = "block_level")]
22 | public long BlockLevel { get; set; }
23 |
24 | [JsonProperty(PropertyName = "operation_hash")]
25 | public string OperationHash { get; set; }
26 |
27 | [JsonProperty(PropertyName = "timestamp")]
28 | public DateTime Timestamp { get; set; }
29 |
30 | [JsonProperty(PropertyName = "origination_content")]
31 | public BlockOriginationContent OriginationContent { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/TransactionModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AgileVentures.TezPusher.Model.RpcEntities;
3 | using Newtonsoft.Json;
4 |
5 | namespace AgileVentures.TezPusher.Model.PushEntities
6 | {
7 | public class TransactionModel
8 | {
9 | public TransactionModel(BlockRpcEntity block, BlockOperation transactionOperation, BlockTransactionContent transactionContent)
10 | {
11 | BlockHash = block.hash;
12 | BlockLevel = block.header.level;
13 | OperationHash = transactionOperation.hash;
14 | Timestamp = block.header.timestamp;
15 | TransactionContent = transactionContent;
16 | }
17 |
18 | [JsonProperty(PropertyName = "block_hash")]
19 | public string BlockHash { get; set; }
20 |
21 | [JsonProperty(PropertyName = "block_level")]
22 | public long BlockLevel { get; set; }
23 |
24 | [JsonProperty(PropertyName = "operation_hash")]
25 | public string OperationHash { get; set; }
26 |
27 | [JsonProperty(PropertyName = "timestamp")]
28 | public DateTime Timestamp { get; set; }
29 |
30 | [JsonProperty(PropertyName = "transaction_content")]
31 | public BlockTransactionContent TransactionContent { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:58599",
7 | "sslPort": 44333
8 | }
9 | },
10 | "$schema": "http://json.schemastore.org/launchsettings.json",
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "api/values",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "AgileVentures.TezPusher.Web": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "api/values",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | },
27 | "applicationUrl": "https://localhost:5001;http://localhost:5000"
28 | },
29 | "Docker": {
30 | "commandName": "Docker",
31 | "launchBrowser": true,
32 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/values",
33 | "environmentVariables": {
34 | "ASPNETCORE_URLS": "https://+:443;http://+:80",
35 | "ASPNETCORE_HTTPS_PORT": "44334"
36 | },
37 | "httpPort": 58604,
38 | "useSSL": true,
39 | "sslPort": 44334
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 | import { AppComponent } from './app.component';
6 | import { SignalRService } from './signalr.service';
7 | import { HttpClientModule } from '@angular/common/http';
8 | import {
9 | MatInputModule,
10 | MatButtonModule,
11 | MatSnackBarModule,
12 | MatTableModule,
13 | MatPaginatorModule,
14 | MatCardModule,
15 | MatDividerModule,
16 | MatProgressBarModule,
17 | MatTabsModule
18 | } from '@angular/material';
19 | import { AmountToTezPipe, FeeToTezPipe } from './pipes';
20 |
21 | @NgModule({
22 | declarations: [
23 | AppComponent,
24 | AmountToTezPipe,
25 | FeeToTezPipe
26 | ],
27 | imports: [
28 | BrowserModule,
29 | HttpClientModule,
30 | FormsModule,
31 | BrowserAnimationsModule,
32 | MatInputModule,
33 | MatButtonModule,
34 | MatSnackBarModule,
35 | MatTableModule,
36 | MatPaginatorModule,
37 | MatCardModule,
38 | MatDividerModule,
39 | MatProgressBarModule,
40 | MatTabsModule
41 | ],
42 | providers: [
43 | SignalRService
44 | ],
45 | bootstrap: [AppComponent]
46 | })
47 | export class AppModule { }
48 |
--------------------------------------------------------------------------------
/docs-sample-clients/docs-agileventures.tezpusher.sampleclient.md:
--------------------------------------------------------------------------------
1 | # Sample Client for TezosLive.io Public Endpoint or Azure Functions
2 |
3 | {% hint style="info" %}
4 | Source code is available on [**GitHub**](https://github.com/agile-ventures/TaaS/blob/master/AgileVentures.TezPusher.SampleClient).
5 | {% endhint %}
6 |
7 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.3. Run `npm i -g @angular/cli` to install angular CLI globally.
8 |
9 | ## How to create your TaaS Endpoint
10 |
11 | To create your own TaaS endpoint please head to [TezosLive.io](https://www.tezoslive.io). You can also try running the sample application with our default endpoint.
12 |
13 | ## How to run
14 |
15 | Run `npm install` to install all required dependencies. Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
16 |
17 | ## Code scaffolding
18 |
19 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
20 |
21 | ## Build
22 |
23 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
29 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [TaaS \(Tezos as a Service\)](README.md)
4 | * [Welcome](docs-welcome.md)
5 |
6 | ## Getting started
7 |
8 | * [Using TezosLive.io Endpoint](docs-getting-started/docs-using-tezoslive.io-endpoint.md)
9 | * [Using Docker](docs-getting-started/docs-using-docker.md)
10 | * [Using Docker with Azure Functions and SignalR Service](docs-getting-started/docs-using-docker-and-serveless.md)
11 |
12 | ## subscribing from clients
13 |
14 | * [Clients - TezosLive.io Endpoint or Azure Functions](docs-clients/docs-taas-endpoint-or-function.md)
15 | * [Clients - Docker](docs-clients/docs-docker.md)
16 | * [Typescript Definitions](docs-clients/docs-typescript-definition.md)
17 |
18 | ## API Endpoints
19 |
20 | * [/api/negotiate](docs-api-endpoints/docs-negotiate.md)
21 | * [/api/subscribe](docs-api-endpoints/docs-api-subscribe.md)
22 | * [/api/unsubscribe](docs-api-endpoints/docs-api-unsubscribe.md)
23 |
24 | ## Sample Clients
25 |
26 | * [Sample Client for TezosLive.io Public Endpoint or Azure Functions](docs-sample-clients/docs-agileventures.tezpusher.sampleclient.md)
27 | * [Sample Client for TaaS Web & Docker](docs-sample-clients/docs-agileventures.tezpusher.sampleclient.web.md)
28 |
29 | ## External Links
30 |
31 | * [TezosLive.io](https://www.tezoslive.io)
32 | * [GitHub](https://github.com/agile-ventures/TaaS)
33 | * [Medium](https://medium.com/tezoslive)
34 |
35 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 | import { AppComponent } from './app.component';
6 | import { SignalRService } from './signalr.service';
7 | import { HttpClientModule } from '@angular/common/http';
8 | import {
9 | MatInputModule,
10 | MatButtonModule,
11 | MatSnackBarModule,
12 | MatTableModule,
13 | MatPaginatorModule,
14 | MatCardModule,
15 | MatDividerModule,
16 | MatProgressBarModule,
17 | MatTabsModule,
18 | MatDialogModule
19 | } from '@angular/material';
20 | import { ServiceUrlDialogComponent } from './serviceurl-dialog/serviceurl-dialog.component';
21 | import { AmountToTezPipe, FeeToTezPipe } from './pipes';
22 |
23 | @NgModule({
24 | declarations: [
25 | AppComponent,
26 | ServiceUrlDialogComponent,
27 | AmountToTezPipe,
28 | FeeToTezPipe
29 | ],
30 | imports: [
31 | BrowserModule,
32 | HttpClientModule,
33 | FormsModule,
34 | BrowserAnimationsModule,
35 | MatInputModule,
36 | MatButtonModule,
37 | MatSnackBarModule,
38 | MatTableModule,
39 | MatPaginatorModule,
40 | MatCardModule,
41 | MatDividerModule,
42 | MatProgressBarModule,
43 | MatTabsModule,
44 | MatDialogModule,
45 | ReactiveFormsModule
46 | ],
47 | providers: [
48 | SignalRService
49 | ],
50 | bootstrap: [AppComponent],
51 | entryComponents: [ServiceUrlDialogComponent]
52 | })
53 | export class AppModule { }
54 |
--------------------------------------------------------------------------------
/docs-sample-clients/docs-agileventures.tezpusher.sampleclient.web.md:
--------------------------------------------------------------------------------
1 | # Sample Client for TaaS Web & Docker
2 |
3 | {% hint style="info" %}
4 | Source code is available on [**GitHub**](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web).
5 | {% endhint %}
6 |
7 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.3. Run `npm i -g @angular/cli` to install angular CLI globally.
8 |
9 | ## Configuration
10 |
11 | Configure `_baseUrl` variable in [src/app/signalr.service.ts\#L60](https://github.com/agile-ventures/TaaS/blob/79552fdaf3df82d6b311112d6651b8e687ba5864/AgileVentures.TezPusher.SampleClient.Web/src/app/signalr.service.ts#L60) based on your environment. If you are running the Docker image from DockerHub \([https://hub.docker.com/r/tezoslive/agileventurestezpusherweb](https://hub.docker.com/r/tezoslive/agileventurestezpusherweb)\) it is easier to use HTTP endpoint at the moment.
12 |
13 | ## How to run
14 |
15 | Run `npm install` to install all required dependencies. Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
16 |
17 | ## Code scaffolding
18 |
19 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
20 |
21 | ## Build
22 |
23 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
29 |
--------------------------------------------------------------------------------
/docs-api-endpoints/docs-api-unsubscribe.md:
--------------------------------------------------------------------------------
1 | # /api/unsubscribe
2 |
3 | {% api-method method="post" host="https://{yourEndpoint}" path="/api/unsubscribe" %}
4 | {% api-method-summary %}
5 | Unsubscribe
6 | {% endapi-method-summary %}
7 |
8 | {% api-method-description %}
9 | This method allows the client to unsubscribe from various update types.
10 | {% endapi-method-description %}
11 |
12 | {% api-method-spec %}
13 | {% api-method-request %}
14 | {% api-method-headers %}
15 | {% api-method-parameter name="Content-Type" type="string" required=false %}
16 | application/json
17 | {% endapi-method-parameter %}
18 | {% endapi-method-headers %}
19 |
20 | {% api-method-body-parameters %}
21 | {% api-method-parameter name="originationAddresses" type="string" required=false %}
22 | Unsubscribe to origination happening on these addresses.
23 | {% endapi-method-parameter %}
24 |
25 | {% api-method-parameter name="delegationAddresses" type="string" required=false %}
26 | Unsubscribe to delegation happening on these addresses.
27 | {% endapi-method-parameter %}
28 |
29 | {% api-method-parameter name="transactionAddresses" type="string" required=false %}
30 | Unsubscribe to transactions happening on these addresses.
31 | {% endapi-method-parameter %}
32 |
33 | {% api-method-parameter name="userId" type="string" required=true %}
34 | UUID used in `/api/negotiate`
35 | {% endapi-method-parameter %}
36 | {% endapi-method-body-parameters %}
37 | {% endapi-method-request %}
38 |
39 | {% api-method-response %}
40 | {% api-method-response-example httpCode=200 %}
41 | {% api-method-response-example-description %}
42 |
43 | {% endapi-method-response-example-description %}
44 |
45 | ```
46 |
47 | ```
48 | {% endapi-method-response-example %}
49 | {% endapi-method-response %}
50 | {% endapi-method-spec %}
51 | {% endapi-method %}
52 |
53 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/RpcEntities/ConstantsRpcEntity.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AgileVentures.TezPusher.Model.Interfaces;
3 |
4 | namespace AgileVentures.TezPusher.Model.RpcEntities
5 | {
6 | public class ConstantsRpcEntity : IRpcEntity
7 | {
8 | public int proof_of_work_nonce_size { get; set; }
9 | public int nonce_length { get; set; }
10 | public int max_revelations_per_block { get; set; }
11 | public int preserved_cycles { get; set; }
12 | public int blocks_per_cycle { get; set; }
13 | public int blocks_per_commitment { get; set; }
14 | public int blocks_per_roll_snapshot { get; set; }
15 | public int blocks_per_voting_period { get; set; }
16 | public List time_between_blocks { get; set; }
17 | public int endorsers_per_block { get; set; }
18 | public string hard_gas_limit_per_operation { get; set; }
19 | public string hard_gas_limit_per_block { get; set; }
20 | public string proof_of_work_threshold { get; set; }
21 | public string dictator_pubkey { get; set; }
22 | public int max_operation_data_length { get; set; }
23 | public string tokens_per_roll { get; set; }
24 | public int michelson_maximum_type_size { get; set; }
25 | public string seed_nonce_revelation_tip { get; set; }
26 | public string origination_burn { get; set; }
27 | public string block_security_deposit { get; set; }
28 | public string endorsement_security_deposit { get; set; }
29 | public string block_reward { get; set; }
30 | public string endorsement_reward { get; set; }
31 | public string cost_per_byte { get; set; }
32 | public string hard_storage_limit_per_operation { get; set; }
33 | public string hard_storage_limit_per_block { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/PushEntities/PushMessage.cs:
--------------------------------------------------------------------------------
1 | using AgileVentures.TezPusher.Model.Constants;
2 | using Newtonsoft.Json;
3 |
4 | namespace AgileVentures.TezPusher.Model.PushEntities
5 | {
6 | public class PushMessage
7 | {
8 | public PushMessage(DelegationModel delegation)
9 | {
10 | Delegation = delegation;
11 | MessageType = PushMessageTypes.DelegationMessageType;
12 | }
13 |
14 | public PushMessage(OriginationModel origination)
15 | {
16 | Origination = origination;
17 | MessageType = PushMessageTypes.OriginationMessageType;
18 | }
19 |
20 | public PushMessage(TransactionModel transaction)
21 | {
22 | Transaction = transaction;
23 | MessageType = PushMessageTypes.TransactionMessageType;
24 | }
25 |
26 | public PushMessage(HeadModel blockHeader)
27 | {
28 | BlockHeader = blockHeader;
29 | MessageType = PushMessageTypes.BlockHeaderMessageType;
30 | }
31 |
32 | [JsonProperty(PropertyName = "block_header", NullValueHandling = NullValueHandling.Ignore)]
33 | public HeadModel BlockHeader { get; set; }
34 |
35 | [JsonProperty(PropertyName = "delegation", NullValueHandling = NullValueHandling.Ignore)]
36 | public DelegationModel Delegation { get; set; }
37 |
38 | [JsonProperty(PropertyName = "origination", NullValueHandling = NullValueHandling.Ignore)]
39 | public OriginationModel Origination { get; set; }
40 |
41 | [JsonProperty(PropertyName = "transaction", NullValueHandling = NullValueHandling.Ignore)]
42 | public TransactionModel Transaction { get; set; }
43 |
44 | [JsonProperty(PropertyName = "message_type")]
45 | public string MessageType { get; set; }
46 | }
47 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.ConsoleApp/AgileVentures.TezPusher.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 | Exe
29 | netcoreapp2.2
30 | Linux
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/docs-api-endpoints/docs-api-subscribe.md:
--------------------------------------------------------------------------------
1 | # /api/subscribe
2 |
3 | {% api-method method="post" host="https://{yourEndpoint}" path="/api/subscribe" %}
4 | {% api-method-summary %}
5 | Subscribe
6 | {% endapi-method-summary %}
7 |
8 | {% api-method-description %}
9 | This method allows the client to subscribe to various update types.
10 | {% endapi-method-description %}
11 |
12 | {% api-method-spec %}
13 | {% api-method-request %}
14 | {% api-method-headers %}
15 | {% api-method-parameter name="Content-Type" type="string" required=false %}
16 | application/json
17 | {% endapi-method-parameter %}
18 | {% endapi-method-headers %}
19 |
20 | {% api-method-body-parameters %}
21 | {% api-method-parameter name="originationAddresses" type="string" required=false %}
22 | Subscribe to origination happening on these addresses.
23 | {% endapi-method-parameter %}
24 |
25 | {% api-method-parameter name="delegationAddresses" type="string" required=false %}
26 | Subscribe to delegation happening on these addresses.
27 | {% endapi-method-parameter %}
28 |
29 | {% api-method-parameter name="transactionAddresses" type="string" required=false %}
30 | Subscribe to transactions happening on these addresses.
31 | {% endapi-method-parameter %}
32 |
33 | {% api-method-parameter name="userId" type="string" required=true %}
34 | UUID used in `/api/negotiate`
35 | {% endapi-method-parameter %}
36 | {% endapi-method-body-parameters %}
37 | {% endapi-method-request %}
38 |
39 | {% api-method-response %}
40 | {% api-method-response-example httpCode=200 %}
41 | {% api-method-response-example-description %}
42 |
43 | {% endapi-method-response-example-description %}
44 |
45 | ```
46 |
47 | ```
48 | {% endapi-method-response-example %}
49 | {% endapi-method-response %}
50 | {% endapi-method-spec %}
51 | {% endapi-method %}
52 |
53 | {% hint style="info" %}
54 | You can subscribe to **all** addresses by sending `['all']` in any of the optional parameters.
55 | {% endhint %}
56 |
57 |
--------------------------------------------------------------------------------
/docs-api-endpoints/docs-negotiate.md:
--------------------------------------------------------------------------------
1 | # /api/negotiate
2 |
3 | {% api-method method="get" host="https://{yourEndpoint}" path="/api/negotiate" %}
4 | {% api-method-summary %}
5 | Negotiate
6 | {% endapi-method-summary %}
7 |
8 | {% api-method-description %}
9 | Calling the `negotiate` endpoint along with generated UUID in `x-tezos-live-userid` HTTP header will return `url` and `accessToken`in the response, which you will use for SignalR hub connection.
10 | {% endapi-method-description %}
11 |
12 | {% api-method-spec %}
13 | {% api-method-request %}
14 | {% api-method-headers %}
15 | {% api-method-parameter name="x-tezos-live-userid" type="string" required=true %}
16 | UUID client identification
17 | {% endapi-method-parameter %}
18 | {% endapi-method-headers %}
19 | {% endapi-method-request %}
20 |
21 | {% api-method-response %}
22 | {% api-method-response-example httpCode=200 %}
23 | {% api-method-response-example-description %}
24 |
25 | {% endapi-method-response-example-description %}
26 |
27 | ```
28 | {
29 | "url":"https://{yourEndpoint}.signalr.net/client/?hub=broadcast",
30 | "accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
31 | }
32 | ```
33 | {% endapi-method-response-example %}
34 | {% endapi-method-response %}
35 | {% endapi-method-spec %}
36 | {% endapi-method %}
37 |
38 | {% hint style="info" %}
39 | You will need the
40 |
41 | * `url` Response parameter for SignalR hub connection
42 | * `accessToken` Response parameter for SignalR hub connection
43 | * `x-tezos-live-userid` parameter for [Subscribe](docs-api-subscribe.md)/[Unsubscribe ](docs-api-unsubscribe.md)calls
44 | {% endhint %}
45 |
46 | For generating UUIDs you can use any library, that complies with [RFC4122](https://www.ietf.org/rfc/rfc4122.txt).
47 | For example [https://www.npmjs.com/package/uuid](https://www.npmjs.com/package/uuid).
48 | You can also use different method for generating userIds, as long as you can ensure, that each client will have unique id.
49 |
50 |
--------------------------------------------------------------------------------
/docs-clients/docs-taas-endpoint-or-function.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: If you are using TezosLive.io Endpoint or Azure Functions.
3 | ---
4 |
5 | # Clients - TezosLive.io Endpoint or Azure Functions
6 |
7 | ### Sample Client
8 |
9 | For reference please take a look at [AgileVentures.TezPusher.SampleClient](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient).
10 | Or you can check out deployed version of this app available at [https://client-staging.tezoslive.io/](https://client-staging.tezoslive.io/).
11 |
12 | ### 1. Getting the url and accessToken from the /api/negotiate
13 |
14 | {% page-ref page="../docs-api-endpoints/docs-negotiate.md" %}
15 |
16 | ### 2. Connecting to the SignalR Hub
17 |
18 | {% hint style="warning" %}
19 | You need a SignalR client library. In this sample we are using [https://www.npmjs.com/package/@aspnet/signalr](https://www.npmjs.com/package/@aspnet/signalr).
20 |
21 | Your usage may vary depending on your programming language and used client library.
22 | {% endhint %}
23 |
24 | Using the data from the `/api/negotiate` response we can now connect to a SignalR hub, where
25 |
26 | * `response.url` is the `url` parameter from `/api/negotiate` response call
27 | * `response.accessToken` is the `accessToken` parameter from`/api/negotiate` response call.
28 |
29 | ```text
30 | this.hubConnection = new signalR.HubConnectionBuilder()
31 | .withUrl(response.url, { accessTokenFactory: () => response.accessToken })
32 | .configureLogging(signalR.LogLevel.Information)
33 | .build();
34 |
35 | this.hubConnection.start().catch(err => console.error(err.toString()));
36 | ```
37 |
38 | You can also check our Sample Client [source code](https://github.com/agile-ventures/TaaS/blob/c961382c1bf5815633da7e1ba0c4865fbe65873e/AgileVentures.TezPusher.SampleClient/src/app/signalr.service.ts#L146) and [SignalR Client Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/client-features?view=aspnetcore-3.0).
39 |
40 | ### 3. Subscribing to updates
41 |
42 | {% page-ref page="../docs-api-endpoints/docs-api-subscribe.md" %}
43 |
44 | ### 4. Unsubscribe from updates
45 |
46 | {% page-ref page="../docs-api-endpoints/docs-api-unsubscribe.md" %}
47 |
48 | ###
49 |
50 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
2 | import { SignalRService, Block, Transaction, Origination, Delegation } from './signalr.service';
3 | import { MatTable } from '@angular/material';
4 |
5 | @Component({
6 | selector: 'app-root',
7 | templateUrl: './app.component.html'
8 | })
9 |
10 | export class AppComponent implements OnInit, OnDestroy {
11 |
12 | private readonly _signalRService: SignalRService;
13 | blocks: Block[] = [];
14 | transactions: Transaction[] = [];
15 | originations: Origination[] = [];
16 | delegations: Delegation[] = [];
17 | displayedColumnsTableBlocks: string[] = ['level', 'hash', 'timestamp', 'validation_pass'];
18 | displayedColumnsTableTransaction: string[] = ['source', 'destination', 'amount', 'timestamp', 'level'];
19 | displayedColumnsTableOrigination: string[] = ['source', 'originated', 'fee', 'timestamp', 'level'];
20 | displayedColumnsTableDelegation: string[] = ['source', 'delegate', 'fee', 'timestamp', 'level'];
21 |
22 |
23 | constructor(signalRService: SignalRService) {
24 | this._signalRService = signalRService;
25 | }
26 |
27 | @ViewChild('tableBlocks', {static: false}) tableBlocks: MatTable;
28 | @ViewChild('tableTransactions', {static: false}) tableTransactions: MatTable;
29 | @ViewChild('tableOriginations', {static: false}) tableOriginations: MatTable;
30 | @ViewChild('tableDelegations', {static: false}) tableDelegations: MatTable;
31 |
32 | ngOnInit() {
33 | this.blocks = [];
34 | this.transactions = [];
35 | this._signalRService.init();
36 |
37 | this._signalRService.blocks.subscribe(block => {
38 | this.blocks.unshift(block);
39 | this.tableBlocks.renderRows();
40 | });
41 |
42 | this._signalRService.transactions.subscribe(transaction => {
43 | this.transactions.unshift(transaction);
44 | this.tableTransactions.renderRows();
45 | });
46 |
47 | this._signalRService.originations.subscribe(origination => {
48 | this.originations.unshift(origination);
49 | this.tableOriginations.renderRows();
50 | });
51 |
52 | this._signalRService.delegations.subscribe(delegation => {
53 | this.delegations.unshift(delegation);
54 | this.tableDelegations.renderRows();
55 | });
56 | }
57 |
58 | ngOnDestroy() {
59 | this._signalRService.blocks.unsubscribe();
60 | this._signalRService.transactions.unsubscribe();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/docs-getting-started/docs-using-docker.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: If you want to host your TaaS endpoint yourself in Docker.
3 | ---
4 |
5 | # Using Docker
6 |
7 | ## Running TaaS in Docker
8 |
9 | {% hint style="danger" %}
10 | **Requirements**
11 |
12 | * Docker
13 | * Tezos Node with enabled RPC endpoint supporting following calls
14 | * _/monitor/heads/main_
15 | * _/chains/main/blocks/hash_
16 | {% endhint %}
17 |
18 | Ready-to-use docker image is available from Docker Hub here: [https://hub.docker.com/r/tezoslive/agileventurestezpusherweb](https://hub.docker.com/r/tezoslive/agileventurestezpusherweb).
19 |
20 | Example of the `docker run` command
21 |
22 | * exposing port 80
23 | * setting the Tezos:NodeUrl environment variable to http://172.17.0.1:8732
24 |
25 | {% hint style="warning" %}
26 | Do not forget to change the **Tezos:NodeUrl** based on your configuration!
27 | {% endhint %}
28 |
29 | ```text
30 | docker run --rm -it -p 80:80 \
31 | --env Tezos:NodeUrl="http://172.17.0.1:8732" \
32 | tezoslive/agileventurestezpusherweb
33 | ```
34 |
35 | #### Optional Configuration
36 |
37 | {% hint style="info" %}
38 | By providing ENV variable **Logging:LogLevel:Default** you can configure logging level.
39 |
40 | * Trace
41 | * Debug
42 | * Information
43 | * Warning
44 | * Error
45 | * Critical
46 | {% endhint %}
47 |
48 | **For client side instructions** please see
49 |
50 | {% page-ref page="../docs-clients/docs-docker.md" %}
51 |
52 | #### Configure SSL/TLS for Your TaaS Docker Image
53 |
54 | {% hint style="warning" %}
55 | If you are considering opening up your ports to the public, you should configure a certificate and only expose **HTTPS** endpoint to the outside.
56 | {% endhint %}
57 |
58 | Example of the `docker run` command for Linux
59 |
60 | * exposing ports 8000 \(http\) and 8001\(https\)
61 | * setting certificate path and password
62 |
63 | ```text
64 | docker run --rm -it -p 8000:80 -p 8001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=8001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="password" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v ${HOME}/.aspnet/https:/https/ tezoslive/agileventurestezpusherweb
65 | ```
66 |
67 | For further information about setting up the certificates please refer to [https://docs.microsoft.com/en-us/aspnet/core/security/docker-https?view=aspnetcore-3.0](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https?view=aspnetcore-3.0).
68 |
69 | For development with Docker over HTTPS please refer to [https://github.com/dotnet/dotnet-docker/blob/master/samples/aspnetapp/aspnetcore-docker-https-development.md](https://github.com/dotnet/dotnet-docker/blob/master/samples/aspnetapp/aspnetcore-docker-https-development.md).
70 |
71 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "messagingdemoclient": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "inlineStyle": true,
14 | "styleext": "less"
15 | }
16 | },
17 | "architect": {
18 | "build": {
19 | "builder": "@angular-devkit/build-angular:browser",
20 | "options": {
21 | "outputPath": "dist/messagingdemoclient",
22 | "index": "src/index.html",
23 | "main": "src/main.ts",
24 | "polyfills": "src/polyfills.ts",
25 | "tsConfig": "src/tsconfig.app.json",
26 | "assets": [
27 | "src/favicon.ico",
28 | "src/assets"
29 | ],
30 | "styles": [
31 | "src/styles.less"
32 | ],
33 | "scripts": []
34 | },
35 | "configurations": {
36 | "production": {
37 | "fileReplacements": [
38 | {
39 | "replace": "src/environments/environment.ts",
40 | "with": "src/environments/environment.prod.ts"
41 | }
42 | ],
43 | "optimization": true,
44 | "outputHashing": "all",
45 | "sourceMap": false,
46 | "extractCss": true,
47 | "namedChunks": false,
48 | "aot": true,
49 | "extractLicenses": true,
50 | "vendorChunk": false,
51 | "buildOptimizer": true
52 | }
53 | }
54 | },
55 | "serve": {
56 | "builder": "@angular-devkit/build-angular:dev-server",
57 | "options": {
58 | "browserTarget": "messagingdemoclient:build"
59 | },
60 | "configurations": {
61 | "production": {
62 | "browserTarget": "messagingdemoclient:build:production"
63 | }
64 | }
65 | },
66 | "extract-i18n": {
67 | "builder": "@angular-devkit/build-angular:extract-i18n",
68 | "options": {
69 | "browserTarget": "messagingdemoclient:build"
70 | }
71 | },
72 | "lint": {
73 | "builder": "@angular-devkit/build-angular:tslint",
74 | "options": {
75 | "tsConfig": [
76 | "src/tsconfig.app.json",
77 | "src/tsconfig.spec.json"
78 | ],
79 | "exclude": [
80 | "**/node_modules/**"
81 | ]
82 | }
83 | }
84 | }
85 | }
86 | },
87 | "defaultProject": "serverlessrealtimedemoclient"
88 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29006.145
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileVentures.TezPusher.Function", "AgileVentures.TezPusher.Function\AgileVentures.TezPusher.Function.csproj", "{A6ACC83D-D5DE-47AE-8D4C-845AA2A7D4D3}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileVentures.TezPusher.Model", "AgileVentures.TezPusher.Model\AgileVentures.TezPusher.Model.csproj", "{413F8EAE-FE75-44BE-A5BD-45FB51443644}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileVentures.TezPusher.Web", "AgileVentures.TezPusher.Web\AgileVentures.TezPusher.Web.csproj", "{1EBEEF18-D1B8-45CC-8ED1-2A9E59AF903B}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileVentures.TezPusher.ConsoleApp", "AgileVentures.TezPusher.ConsoleApp\AgileVentures.TezPusher.ConsoleApp.csproj", "{F25CA9E0-5733-4EDB-ADBE-191336E1A6D3}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {A6ACC83D-D5DE-47AE-8D4C-845AA2A7D4D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {A6ACC83D-D5DE-47AE-8D4C-845AA2A7D4D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {A6ACC83D-D5DE-47AE-8D4C-845AA2A7D4D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {A6ACC83D-D5DE-47AE-8D4C-845AA2A7D4D3}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {413F8EAE-FE75-44BE-A5BD-45FB51443644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {413F8EAE-FE75-44BE-A5BD-45FB51443644}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {413F8EAE-FE75-44BE-A5BD-45FB51443644}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {413F8EAE-FE75-44BE-A5BD-45FB51443644}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {1EBEEF18-D1B8-45CC-8ED1-2A9E59AF903B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {1EBEEF18-D1B8-45CC-8ED1-2A9E59AF903B}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {1EBEEF18-D1B8-45CC-8ED1-2A9E59AF903B}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {1EBEEF18-D1B8-45CC-8ED1-2A9E59AF903B}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {F25CA9E0-5733-4EDB-ADBE-191336E1A6D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {F25CA9E0-5733-4EDB-ADBE-191336E1A6D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {F25CA9E0-5733-4EDB-ADBE-191336E1A6D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {F25CA9E0-5733-4EDB-ADBE-191336E1A6D3}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {0D731480-2B0B-4997-976A-B0DCD9C22F92}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "messagingdemoclient": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "inlineStyle": true,
14 | "styleext": "less"
15 | }
16 | },
17 | "architect": {
18 | "build": {
19 | "builder": "@angular-devkit/build-angular:browser",
20 | "options": {
21 | "outputPath": "dist/messagingdemoclient",
22 | "index": "src/index.html",
23 | "main": "src/main.ts",
24 | "polyfills": "src/polyfills.ts",
25 | "tsConfig": "src/tsconfig.app.json",
26 | "assets": [
27 | "src/favicon.ico",
28 | "src/assets"
29 | ],
30 | "styles": [
31 | "src/styles.less"
32 | ],
33 | "scripts": []
34 | },
35 | "configurations": {
36 | "production": {
37 | "fileReplacements": [
38 | {
39 | "replace": "src/environments/environment.ts",
40 | "with": "src/environments/environment.prod.ts"
41 | }
42 | ],
43 | "optimization": true,
44 | "outputHashing": "all",
45 | "sourceMap": false,
46 | "extractCss": true,
47 | "namedChunks": false,
48 | "aot": true,
49 | "extractLicenses": true,
50 | "vendorChunk": false,
51 | "buildOptimizer": true
52 | }
53 | }
54 | },
55 | "serve": {
56 | "builder": "@angular-devkit/build-angular:dev-server",
57 | "options": {
58 | "browserTarget": "messagingdemoclient:build"
59 | },
60 | "configurations": {
61 | "production": {
62 | "browserTarget": "messagingdemoclient:build:production"
63 | }
64 | }
65 | },
66 | "extract-i18n": {
67 | "builder": "@angular-devkit/build-angular:extract-i18n",
68 | "options": {
69 | "browserTarget": "messagingdemoclient:build"
70 | }
71 | },
72 | "lint": {
73 | "builder": "@angular-devkit/build-angular:tslint",
74 | "options": {
75 | "tsConfig": [
76 | "src/tsconfig.app.json",
77 | "src/tsconfig.spec.json"
78 | ],
79 | "exclude": [
80 | "**/node_modules/**"
81 | ]
82 | }
83 | }
84 | }
85 | }
86 | },
87 | "defaultProject": "serverlessrealtimedemoclient"
88 | }
--------------------------------------------------------------------------------
/docs-getting-started/docs-using-docker-and-serveless.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | If you want to use Serverless Azure Functions and Azure SignalR Service with
4 | TaaS
5 | ---
6 |
7 | # Using Docker with Azure Functions and SignalR Service
8 |
9 | ## Running TaaS with Azure Functions and Azure SignalR Service
10 |
11 | {% hint style="danger" %}
12 | **Requirements**
13 |
14 | * Azure Account \([https://azure.microsoft.com/en-us/free/](https://azure.microsoft.com/en-us/free/)\)
15 | * Docker
16 | * Tezos Node with enabled RPC endpoint supporting following calls
17 | * _/monitor/heads/main_
18 | * _/chains/main/blocks/hash_
19 | {% endhint %}
20 |
21 | In this configuration we are using a different docker image - this one is only sending a new blocks to our Azure Function, which then sends parsed information to subscribers through Azure SignalR Service.
22 |
23 | {% hint style="info" %}
24 | If you just want to use a Docker container without having to setup Azure infrastructure please check [Using Docker](docs-using-docker.md) documentation section.
25 | {% endhint %}
26 |
27 | Ready-to-use docker image is available from Docker Hub here:
28 | [https://hub.docker.com/repository/docker/tezoslive/agileventurestezpusherconsoleapp](https://hub.docker.com/repository/docker/tezoslive/agileventurestezpusherconsoleapp)
29 |
30 | Example of the `docker run` command
31 |
32 | * setting the `Tezos:NodeUrl` env. variable to https://172.17.0.1:8732
33 | * setting the `Azure:AzureFunctionUrl` env. variable to [https://myfunction.azurewebsites.net](https://myfunction.azurewebsites.net)
34 | * setting the `Azure:AzureFunctionKey` env. variable to MySecretFunctionKey
35 |
36 | {% hint style="warning" %}
37 | Be sure to configure the following ENV keys correctly per your environment
38 |
39 | * `Tezos:NodeUrl`
40 | * `Azure:AzureFunctionUrl`
41 | * `Azure:AzureFunctionKey`
42 | {% endhint %}
43 |
44 | ```text
45 | docker run -it --env Tezos:NodeUrl="https://172.17.0.1:8732" \
46 | --env Azure:AzureFunctionUrl="https://myfunction.azurewebsites.net" \
47 | --env Azure:AzureFunctionKey="MySecretFunctionKey" \
48 | tezoslive/agileventurestezpusherconsoleapp
49 | ```
50 |
51 | This infrastructure setup has the following benefits
52 |
53 | * It allows you to have a more robust security out of the box as all communication is encrypted by TLS.
54 | * It makes scaling your applications for thousands of subscribers much easier by using serveless compute with scalable SignalR Service.
55 |
56 | **For client side instructions** please see
57 |
58 | {% page-ref page="../docs-clients/docs-taas-endpoint-or-function.md" %}
59 |
60 | {% hint style="info" %}
61 | More information about **Azure Functions** can be found at [https://azure.microsoft.com/en-us/services/functions/](https://azure.microsoft.com/en-us/services/functions/).
62 |
63 | More information about **Azure SignalR Service** can be found at [https://azure.microsoft.com/en-us/services/signalr-service/](https://azure.microsoft.com/en-us/services/signalr-service/).
64 | {% endhint %}
65 |
66 |
--------------------------------------------------------------------------------
/docs-clients/docs-docker.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | If you are using TezPusher.Web in Docker or as a stand-alone ASP.Net Core
4 | Application.
5 | ---
6 |
7 | # Clients - Docker
8 |
9 | ### Sample Client
10 |
11 | For reference please take a look at [AgileVentures.TezPusher.SampleClient.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web).
12 |
13 | ### 1. Connect to SignalR Hub
14 |
15 | You can connect to the hub for example like this \(see [`signalr.service.ts`](https://github.com/agile-ventures/TaaS/blob/84fe386b38f5e488a194a2aa531b109c7dc435d6/AgileVentures.TezPusher.SampleClient.Web/src/app/signalr.service.ts#L65)\).
16 |
17 | {% hint style="warning" %}
18 | You need a SignalR client library. In this sample we are using [https://www.npmjs.com/package/@aspnet/signalr](https://www.npmjs.com/package/@aspnet/signalr).
19 |
20 | Your usage may vary depending on your programming language and used client library.
21 | {% endhint %}
22 |
23 | ```typescript
24 | private connect(): Observable {
25 | this.hubConnection = new signalR.HubConnectionBuilder()
26 | .withUrl(`${this._baseUrl}/tezosHub`)
27 | .configureLogging(signalR.LogLevel.Information)
28 | .build();
29 | return from(this.hubConnection.start());
30 | }
31 | ```
32 |
33 | ### 2. Subscribe to updates
34 |
35 | You can then subscribe to events like this. You can only specify the event types you are interested in.
36 |
37 | ```typescript
38 | this.hubConnection.send("subscribe", {
39 | transactionAddresses: ['all'],
40 | delegationAddresses: ['all'],
41 | originationAddresses: ['all'],
42 | fromBlockLevel: 744190,
43 | blockHeaders: true
44 | });
45 | ```
46 |
47 | Note: `transactionAddresses`, `delegationAddresses` and `originationAdresses` are `string[]`.
48 |
49 | {% hint style="warning" %}
50 | If your client missed some blocks and you want to continue where you left off, you can optionally specify **fromBlockLevel** parameter. This will first send all the subscribed information from the blocks starting at the specified block level.
51 | {% endhint %}
52 |
53 | {% hint style="info" %}
54 | * Specifying **'all'** will subscribe the client to all transactions/delegations/originations respectively.
55 | * Using specific addresses in the arrays will only subscribe to events happening on these addresses.
56 | {% endhint %}
57 |
58 | For reference please take a look at [AgileVentures.TezPusher.SampleClient.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web) specifically [`signalr.service.ts`](https://github.com/agile-ventures/TaaS/blob/84fe386b38f5e488a194a2aa531b109c7dc435d6/AgileVentures.TezPusher.SampleClient.Web/src/app/signalr.service.ts#L65).
59 |
60 | ### 3. Unsubscribe from updates
61 |
62 | You can unsubscribe from events similarly to subscribing.
63 |
64 | ```typescript
65 | this.hubConnection.send("unsubscribe", {
66 | transactionAddresses: ['all'],
67 | delegationAddresses: ['all'],
68 | originationAddresses: ['all']
69 | });
70 | ```
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Services/TezosHistoryService.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 | using AgileVentures.TezPusher.Model.PushEntities;
6 | using AgileVentures.TezPusher.Model.RpcEntities;
7 | using AgileVentures.TezPusher.Web.Configurations;
8 | using AgileVentures.TezPusher.Web.HttpClients;
9 | using Microsoft.AspNetCore.SignalR;
10 | using Microsoft.Extensions.Logging;
11 | using Microsoft.Extensions.Options;
12 | using Newtonsoft.Json;
13 |
14 | namespace AgileVentures.TezPusher.Web.Services
15 | {
16 | public interface ITezosHistoryService
17 | {
18 | Task ProcessHistoryAsync(IClientProxy clientsCaller, string contextConnectionId, SubscribeModel model);
19 | }
20 |
21 | public class TezosHistoryService : ITezosHistoryService
22 | {
23 | private readonly ILogger _logger;
24 | private readonly HttpClient _tezosMonitorClient;
25 | private readonly TezosConfig _tezosConfig;
26 | private readonly IPushHistoryService _pushHistoryService;
27 |
28 | private const string TezosBlockUriTemplate = "{0}/chains/main/blocks/{1}";
29 |
30 | public TezosHistoryService(ILogger logger, TezosMonitorClient tezosMonitorClient, IOptions tezosConfig, IPushHistoryService pushHistoryService)
31 | {
32 | _logger = logger;
33 | _tezosMonitorClient = tezosMonitorClient.Client;
34 | _tezosConfig = tezosConfig.Value;
35 | _pushHistoryService = pushHistoryService;
36 | }
37 |
38 | public async Task ProcessHistoryAsync(IClientProxy clientsCaller, string contextConnectionId, SubscribeModel model)
39 | {
40 | _logger.LogInformation($"Start processing history from block {model.FromBlockLevel} for connectionId {contextConnectionId}.");
41 | HttpResponseMessage response;
42 | Debug.Assert(model.FromBlockLevel != null, "model.FromBlockLevel != null");
43 | var blockLevel = model.FromBlockLevel.Value;
44 | do
45 | {
46 | response = await _tezosMonitorClient.GetAsync(string.Format(TezosBlockUriTemplate, _tezosConfig.NodeUrl, blockLevel));
47 | if (response.StatusCode == HttpStatusCode.OK)
48 | {
49 | var block = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
50 | _logger.LogInformation($"Processing history | Block={block.metadata.level}.");
51 |
52 | await _pushHistoryService.PushBlockHeader(clientsCaller, new HeadModel(block));
53 | await _pushHistoryService.PushOperations(clientsCaller, block, model);
54 | }
55 |
56 | blockLevel++;
57 | } while (response.StatusCode != HttpStatusCode.NotFound);
58 | _logger.LogInformation($"Finished processing history from block {model.FromBlockLevel} for connectionId {contextConnectionId}.");
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, ViewChild, ChangeDetectorRef, AfterViewInit } from '@angular/core';
2 | import { SignalRService, Block, Transaction, Origination, Delegation } from './signalr.service';
3 | import { MatTable, MatDialogConfig, MatDialog } from '@angular/material';
4 | import { ServiceUrlDialogComponent } from './serviceurl-dialog/serviceurl-dialog.component';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html'
9 | })
10 |
11 | export class AppComponent implements AfterViewInit, OnDestroy {
12 |
13 | blocks: Block[] = [];
14 | transactions: Transaction[] = [];
15 | originations: Origination[] = [];
16 | delegations: Delegation[] = [];
17 | displayedColumnsTableBlocks: string[] = ['level', 'hash', 'timestamp', 'validation_pass'];
18 | displayedColumnsTableTransaction: string[] = ['source', 'destination', 'amount', 'timestamp', 'level'];
19 | displayedColumnsTableOrigination: string[] = ['source', 'originated', 'fee', 'timestamp', 'level'];
20 | displayedColumnsTableDelegation: string[] = ['source', 'delegate', 'fee', 'timestamp', 'level'];
21 |
22 | defaultEndpointUrl = 'https://taas-staging.tezoslive.io/api/';
23 |
24 | constructor(private signalRService: SignalRService, private dialog: MatDialog, private cdr: ChangeDetectorRef) { }
25 |
26 | @ViewChild('tableBlocks', {static: false}) tableBlocks: MatTable;
27 | @ViewChild('tableTransactions', {static: false}) tableTransactions: MatTable;
28 | @ViewChild('tableOriginations', {static: false}) tableOriginations: MatTable;
29 | @ViewChild('tableDelegations', {static: false}) tableDelegations: MatTable;
30 |
31 | ngAfterViewInit() {
32 | const dialogConfig = new MatDialogConfig();
33 | dialogConfig.data = { endpoint: this.defaultEndpointUrl };
34 |
35 | setTimeout(() => {
36 | const dialogRef = this.dialog.open(ServiceUrlDialogComponent, dialogConfig);
37 | dialogRef.afterClosed().subscribe(this.onDialogClose);
38 | }, 0);
39 | this.cdr.detectChanges();
40 | }
41 |
42 | ngOnDestroy() {
43 | this.signalRService.blocks.unsubscribe();
44 | this.signalRService.transactions.unsubscribe();
45 | this.signalRService.originations.unsubscribe();
46 | this.signalRService.delegations.unsubscribe();
47 | }
48 |
49 | private onDialogClose = (dialogEndpointUrl: string) => {
50 |
51 | if (dialogEndpointUrl && !dialogEndpointUrl.endsWith('/')) {
52 | dialogEndpointUrl += '/';
53 | }
54 | this.signalRService.init(dialogEndpointUrl ? dialogEndpointUrl : this.defaultEndpointUrl);
55 |
56 | this.signalRService.blocks.subscribe(block => {
57 | this.blocks.unshift(block);
58 | this.tableBlocks.renderRows();
59 | });
60 |
61 | this.signalRService.transactions.subscribe(transaction => {
62 | this.transactions.unshift(transaction);
63 | this.tableTransactions.renderRows();
64 | });
65 |
66 | this.signalRService.originations.subscribe(origination => {
67 | this.originations.unshift(origination);
68 | this.tableOriginations.renderRows();
69 | });
70 |
71 | this.signalRService.delegations.subscribe(delegation => {
72 | this.delegations.unshift(delegation);
73 | this.tableDelegations.renderRows();
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/AgileVentures.TezPusher.Pusher/AgileVentures.TezPusher.Pusher.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/AgileVentures.TezPusher.Pusher/AgileVentures.TezPusher.Pusher.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/AgileVentures.TezPusher.Pusher/AgileVentures.TezPusher.Pusher.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | },
41 | {
42 | "label": "clean",
43 | "command": "dotnet clean",
44 | "type": "shell",
45 | "problemMatcher": "$msCompile",
46 | "options": {
47 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Function"
48 | }
49 | },
50 | {
51 | "label": "build",
52 | "command": "dotnet build",
53 | "type": "shell",
54 | "dependsOn": "clean",
55 | "group": {
56 | "kind": "build",
57 | "isDefault": true
58 | },
59 | "problemMatcher": "$msCompile",
60 | "options": {
61 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Function"
62 | }
63 | },
64 | {
65 | "label": "clean release",
66 | "command": "dotnet clean --configuration Release",
67 | "type": "shell",
68 | "problemMatcher": "$msCompile",
69 | "options": {
70 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Function"
71 | }
72 | },
73 | {
74 | "label": "publish",
75 | "command": "dotnet publish --configuration Release",
76 | "type": "shell",
77 | "dependsOn": "clean release",
78 | "problemMatcher": "$msCompile",
79 | "options": {
80 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Function"
81 | }
82 | },
83 | {
84 | "type": "func",
85 | "dependsOn": "build",
86 | "options": {
87 | "cwd": "${workspaceFolder}/AgileVentures.TezPusher.Function/bin/Debug/netstandard2.0"
88 | },
89 | "command": "host start",
90 | "isBackground": true,
91 | "problemMatcher": "$func-watch"
92 | }
93 | ]
94 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Model/RpcEntities/DelegateRpcModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AgileVentures.TezPusher.Model.Interfaces;
3 |
4 | namespace AgileVentures.TezPusher.Model.RpcEntities
5 | {
6 | ///
7 | /// The total amount of frozen deposits/rewards/fees.
8 | ///
9 | public class FrozenBalanceByCycle
10 | {
11 | public long cycle { get; set; }
12 | public string deposit { get; set; }
13 | public string fees { get; set; }
14 | public string rewards { get; set; }
15 | }
16 |
17 | public class DelegateRpcEntity : IRpcEntity
18 | {
19 | /// The full balance of a delegate: i.e.the balance of its delegate account plus the total amount of frozen deposits/rewards/fees.
20 | ///
21 | public string balance { get; set; }
22 |
23 | ///
24 | /// The details of frozen deposits/rewards/fees indexed by the cycle !!!at the end of which!!! they will be unfrozen.
25 | ///
26 | public string frozen_balance { get; set; }
27 |
28 | ///
29 | /// The total amount of frozen deposits/rewards/fees by cycle
30 | ///
31 | public List frozen_balance_by_cycle { get; set; }
32 |
33 | ///
34 | /// The total amount of tokens the delegate stakes for. This includes the balance of all contracts delegated to , but also the delegate own account and the frozen deposits and fees. This does not include the frozen rewards.
35 | ///
36 | public string staking_balance { get; set; }
37 |
38 | ///
39 | /// Returns the list of contracts that delegate to a given delegate.
40 | ///
41 | public List delegated_contracts { get; set; }
42 |
43 | ///
44 | /// Returns the balances of all the contracts that delegate to a given delegate. This excludes the delegate's own balance and its frozen balances.
45 | ///
46 | public string delegated_balance { get; set; }
47 |
48 | ///
49 | /// Tells whether the delegate is currently tagged as deactivated or not.
50 | ///
51 | public bool deactivated { get; set; }
52 |
53 | ///
54 | /// Returns the cycle by the end of which the delegate might be deactivated if she fails to execute any delegate action. A deactivated delegate might be reactivated (without loosing any rolls) by simply re-registering as a delegate. For deactivated delegates, this value contains the cycle by which they were deactivated.
55 | ///
56 | public int grace_period { get; set; }
57 | }
58 |
59 | public class DelegateBalanceRpcEntity : IRpcEntity
60 | {
61 | public string balance { get; set; }
62 | }
63 |
64 | public class DelegatedContractsRpcEntity : IRpcEntity
65 | {
66 | public List delegated_contracts { get; set; }
67 | }
68 |
69 | public class DelegateRpcEntityError
70 | {
71 | public DelegateRpcEntityErrorDetails[] Errors { get; set; }
72 | }
73 |
74 | public class DelegateRpcEntityErrorDetails
75 | {
76 | public string kind { get; set; }
77 | public string id { get; set; }
78 | public List missing_key { get; set; }
79 | public string function { get; set; }
80 | }
81 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | true,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-shadowed-variable": true,
69 | "no-string-literal": false,
70 | "no-string-throw": true,
71 | "no-switch-case-fall-through": true,
72 | "no-trailing-whitespace": true,
73 | "no-unnecessary-initializer": true,
74 | "no-unused-expression": true,
75 | "no-use-before-declare": true,
76 | "no-var-keyword": true,
77 | "object-literal-sort-keys": false,
78 | "one-line": [
79 | true,
80 | "check-open-brace",
81 | "check-catch",
82 | "check-else",
83 | "check-whitespace"
84 | ],
85 | "prefer-const": true,
86 | "quotemark": [
87 | true,
88 | "single"
89 | ],
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always"
94 | ],
95 | "triple-equals": [
96 | true,
97 | "allow-null-check"
98 | ],
99 | "typedef-whitespace": [
100 | true,
101 | {
102 | "call-signature": "nospace",
103 | "index-signature": "nospace",
104 | "parameter": "nospace",
105 | "property-declaration": "nospace",
106 | "variable-declaration": "nospace"
107 | }
108 | ],
109 | "unified-signatures": true,
110 | "variable-name": false,
111 | "whitespace": [
112 | true,
113 | "check-branch",
114 | "check-decl",
115 | "check-operator",
116 | "check-separator",
117 | "check-type"
118 | ],
119 | "no-output-on-prefix": true,
120 | "use-input-property-decorator": true,
121 | "use-output-property-decorator": true,
122 | "use-host-property-decorator": true,
123 | "no-input-rename": true,
124 | "no-output-rename": true,
125 | "use-life-cycle-interface": true,
126 | "use-pipe-transform-interface": true,
127 | "component-class-suffix": true,
128 | "directive-class-suffix": true
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Web Animations `@angular/platform-browser/animations`
51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
53 | **/
54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
55 |
56 | /**
57 | * By default, zone.js will patch all possible macroTask and DomEvents
58 | * user can disable parts of macroTask/DomEvents patch by setting following flags
59 | */
60 |
61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
64 |
65 | /*
66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
68 | */
69 | // (window as any).__Zone_enable_cross_context_check = true;
70 |
71 | /***************************************************************************************************
72 | * Zone JS is required by default for Angular itself.
73 | */
74 | import 'zone.js/dist/zone'; // Included with Angular CLI.
75 |
76 |
77 |
78 | /***************************************************************************************************
79 | * APPLICATION IMPORTS
80 | */
81 | import 'hammerjs/hammer';
82 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Web Animations `@angular/platform-browser/animations`
51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
53 | **/
54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
55 |
56 | /**
57 | * By default, zone.js will patch all possible macroTask and DomEvents
58 | * user can disable parts of macroTask/DomEvents patch by setting following flags
59 | */
60 |
61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
64 |
65 | /*
66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
68 | */
69 | // (window as any).__Zone_enable_cross_context_check = true;
70 |
71 | /***************************************************************************************************
72 | * Zone JS is required by default for Angular itself.
73 | */
74 | import 'zone.js/dist/zone'; // Included with Angular CLI.
75 |
76 |
77 |
78 | /***************************************************************************************************
79 | * APPLICATION IMPORTS
80 | */
81 | import 'hammerjs/hammer';
82 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | true,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-shadowed-variable": true,
69 | "no-string-literal": false,
70 | "no-string-throw": true,
71 | "no-switch-case-fall-through": true,
72 | "no-trailing-whitespace": true,
73 | "no-unnecessary-initializer": true,
74 | "no-unused-expression": true,
75 | "no-use-before-declare": true,
76 | "no-var-keyword": true,
77 | "object-literal-sort-keys": false,
78 | "one-line": [
79 | true,
80 | "check-open-brace",
81 | "check-catch",
82 | "check-else",
83 | "check-whitespace"
84 | ],
85 | "prefer-const": true,
86 | "quotemark": [
87 | true,
88 | "single"
89 | ],
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always"
94 | ],
95 | "triple-equals": [
96 | true,
97 | "allow-null-check"
98 | ],
99 | "typedef-whitespace": [
100 | true,
101 | {
102 | "call-signature": "nospace",
103 | "index-signature": "nospace",
104 | "parameter": "nospace",
105 | "property-declaration": "nospace",
106 | "variable-declaration": "nospace"
107 | }
108 | ],
109 | "unified-signatures": true,
110 | "variable-name": false,
111 | "whitespace": [
112 | true,
113 | "check-branch",
114 | "check-decl",
115 | "check-operator",
116 | "check-separator",
117 | "check-type"
118 | ],
119 | "no-output-on-prefix": true,
120 | "use-input-property-decorator": true,
121 | "use-output-property-decorator": true,
122 | "use-host-property-decorator": true,
123 | "no-input-rename": true,
124 | "no-output-rename": true,
125 | "use-life-cycle-interface": true,
126 | "use-pipe-transform-interface": true,
127 | "component-class-suffix": true,
128 | "directive-class-suffix": true
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Services/TezosMonitorService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net.Http;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using AgileVentures.TezPusher.Model.PushEntities;
7 | using AgileVentures.TezPusher.Model.RpcEntities;
8 | using AgileVentures.TezPusher.Web.Configurations;
9 | using AgileVentures.TezPusher.Web.HttpClients;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 | using Microsoft.Extensions.Options;
13 | using Newtonsoft.Json;
14 |
15 | namespace AgileVentures.TezPusher.Web.Services
16 | {
17 | public class TezosMonitorService : IHostedService
18 | {
19 | private readonly ILogger _logger;
20 | private readonly HttpClient _tezosMonitorClient;
21 | private readonly IPushService _pushService;
22 | private readonly TezosConfig _tezosConfig;
23 | private const string TezosMonitorUriTemplate = "{0}/monitor/heads/main";
24 |
25 | public TezosMonitorService(
26 | ILogger logger, TezosMonitorClient client, IOptions tezosConfig, IPushService pushService)
27 | {
28 | _logger = logger;
29 | _pushService = pushService;
30 | _tezosMonitorClient = client.Client;
31 | _tezosConfig = tezosConfig.Value;
32 | }
33 |
34 | public async Task StartAsync(CancellationToken cancellationToken)
35 | {
36 | _logger.LogInformation("Tezos Monitor Service is starting.");
37 |
38 | var nodeMonitorUrl = string.Format(TezosMonitorUriTemplate, _tezosConfig.NodeUrl);
39 | var request = new HttpRequestMessage(HttpMethod.Get, nodeMonitorUrl);
40 | HttpResponseMessage result;
41 | try
42 | {
43 | result = await _tezosMonitorClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None);
44 | }
45 | catch (Exception e)
46 | {
47 | _logger.LogCritical(e, $"Cannot connect to Tezos Node at {nodeMonitorUrl}");
48 | throw;
49 | }
50 |
51 | var stream = await result.Content.ReadAsStreamAsync();
52 | var sr = new StreamReader(stream);
53 | string line;
54 |
55 | while ((line = sr.ReadLine()) != null)
56 | {
57 | try
58 | {
59 | var head = JsonConvert.DeserializeObject(line);
60 |
61 | var blockString = await _tezosMonitorClient.GetStringAsync(GetBlockUrl(head.hash));
62 |
63 | var block = JsonConvert.DeserializeObject(blockString);
64 |
65 | await _pushService.PushBlockHeader(new HeadModel(block));
66 | await _pushService.PushOperations(block);
67 |
68 | _logger.LogInformation($"Block {head.level} has been sent to clients.");
69 | _logger.LogTrace(line);
70 | }
71 | catch (Exception ex)
72 | {
73 | _logger.LogError(ex, $"Failed to send the block to clients.");
74 | }
75 | }
76 | }
77 |
78 | public Task StopAsync(CancellationToken cancellationToken)
79 | {
80 | _logger.LogInformation("Tezos Monitor Service is stopping.");
81 | return Task.CompletedTask;
82 | }
83 |
84 | private string GetBlockUrl(string hash)
85 | {
86 | return $"{_tezosConfig.NodeUrl}/chains/main/blocks/{hash}";
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using AgileVentures.TezPusher.Web.Configurations;
4 | using AgileVentures.TezPusher.Web.HttpClients;
5 | using AgileVentures.TezPusher.Web.Hubs;
6 | using AgileVentures.TezPusher.Web.Services;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.Extensions.Configuration;
11 | using Microsoft.Extensions.DependencyInjection;
12 |
13 | namespace AgileVentures.TezPusher.Web
14 | {
15 | public class Startup
16 | {
17 | public Startup(IConfiguration configuration)
18 | {
19 | Configuration = configuration;
20 | }
21 |
22 | public IConfiguration Configuration { get; set; }
23 |
24 | // This method gets called by the runtime. Use this method to add services to the container.
25 | public void ConfigureServices(IServiceCollection services)
26 | {
27 | if (string.IsNullOrEmpty(Configuration["Tezos:NodeUrl"]))
28 | {
29 | // User-Secrets: https://docs.asp.net/en/latest/security/app-secrets.html
30 | // See below for registration instructions for each provider.
31 | throw new InvalidOperationException("Tezos NodeUrl must be configured in ENV variables.");
32 | }
33 |
34 | services.AddCors(
35 | options => options.AddPolicy("AllowCors",
36 | builder =>
37 | {
38 | builder
39 | .AllowCredentials()
40 | .AllowAnyHeader()
41 | .AllowAnyMethod()
42 | .SetIsOriginAllowed(isOriginAllowed => true);
43 | })
44 | );
45 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
46 | services.AddHttpContextAccessor();
47 | services.AddOptions();
48 | services.AddLogging();
49 | services.AddOptions().Bind(Configuration.GetSection("Tezos")).ValidateDataAnnotations();
50 | services.AddHttpClient().ConfigurePrimaryHttpMessageHandler(() =>
51 | new HttpClientHandler { AllowAutoRedirect = false, MaxAutomaticRedirections = 20 }
52 | );
53 | services.AddSingleton();
54 | services.AddSingleton();
55 | services.AddSingleton();
56 | services.AddHostedService();
57 | services.AddSignalR();
58 | }
59 |
60 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
61 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
62 | {
63 | if (env.IsDevelopment())
64 | {
65 | app.UseDeveloperExceptionPage();
66 | }
67 | else
68 | {
69 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
70 | app.UseHsts();
71 | }
72 |
73 | app.UseHttpsRedirection();
74 | app.UseCors("AllowCors");
75 | app.UseSignalR(routes =>
76 | {
77 | routes.MapHub("/tezosHub");
78 | });
79 | app.UseMvc();
80 |
81 | var builder = new ConfigurationBuilder()
82 | .AddJsonFile("appsettings.json")
83 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json")
84 | .AddEnvironmentVariables();
85 |
86 | Configuration = builder.Build();
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Service/AzureStorageHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Microsoft.Azure.Cosmos.Table;
5 |
6 | namespace AgileVentures.TezPusher.Service
7 | {
8 | public interface IAzureStorageHelper
9 | {
10 | Task> ReadAllEntities(string tableName, string partitionKey = null, int count = 0) where T : TableEntity, new();
11 |
12 | Task> ReadAllEntitiesByRowKey(string tableName, string rowKey = null, int count = 0) where T : TableEntity, new();
13 | }
14 |
15 | public class AzureStorageHelper : IAzureStorageHelper
16 | {
17 | private Dictionary TableReferences => new Dictionary();
18 | private readonly string _connectionString;
19 |
20 | public AzureStorageHelper(string connectionString)
21 | {
22 | _connectionString = connectionString;
23 | }
24 |
25 | private async Task GetTableReference(string tableName)
26 | {
27 | if (TableReferences.ContainsKey(tableName))
28 | {
29 | return TableReferences[tableName];
30 | }
31 |
32 | var storageAccount = CloudStorageAccount.Parse(_connectionString);
33 | var tableClient = storageAccount.CreateCloudTableClient();
34 | var table = tableClient.GetTableReference(tableName);
35 |
36 | var tableExists = await table.ExistsAsync();
37 | if (!tableExists)
38 | {
39 | // Create the table if it doesn't exist.
40 | //This is always returning 409 and it spams the app insights logs => we're only calling it when table doesn't exists
41 | await table.CreateAsync();
42 | }
43 |
44 | TableReferences.Add(tableName, table);
45 | return table;
46 | }
47 |
48 | public async Task> ReadAllEntities(string tableName, string partitionKey = null, int count = 0) where T : TableEntity, new()
49 | {
50 | var table = await GetTableReference(tableName);
51 | return await ReadAllEntitiesByKeys(table, partitionKey: partitionKey, count: count);
52 | }
53 |
54 | public async Task> ReadAllEntitiesByRowKey(string tableName, string rowKey = null, int count = 0) where T : TableEntity, new()
55 | {
56 | var table = await GetTableReference(tableName);
57 | return await ReadAllEntitiesByKeys(table, rowKey: rowKey, count: count);
58 | }
59 |
60 | private async Task> ReadAllEntitiesByKeys(CloudTable table, string partitionKey = null, string rowKey = null, int count = 0)
61 | where T : TableEntity, new()
62 | {
63 | var entities = new List();
64 | var tableQuery = new TableQuery();
65 | if (!string.IsNullOrEmpty(partitionKey) && string.IsNullOrEmpty(rowKey))
66 | {
67 | tableQuery = new TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));
68 | }
69 | else if (string.IsNullOrEmpty(partitionKey) && !string.IsNullOrEmpty(rowKey))
70 | {
71 | tableQuery = new TableQuery().Where(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, rowKey));
72 | }
73 | else if (!string.IsNullOrEmpty(partitionKey) && !string.IsNullOrEmpty(rowKey))
74 | {
75 | throw new ArgumentOutOfRangeException($"Use ReadEntity if you have both keys!");
76 | }
77 |
78 | if (count > 0)
79 | {
80 | tableQuery.TakeCount = count;
81 | }
82 |
83 | TableContinuationToken token = null;
84 | do
85 | {
86 | var queryResult = await table.ExecuteQuerySegmentedAsync(tableQuery, token);
87 | entities.AddRange(queryResult.Results);
88 | token = queryResult.ContinuationToken;
89 | } while (count > 0 ? token != null && entities.Count <= count : token != null);
90 |
91 | return entities;
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Hubs/TezosHub.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using AgileVentures.TezPusher.Model.PushEntities;
4 | using AgileVentures.TezPusher.Web.Services;
5 | using Microsoft.AspNetCore.SignalR;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace AgileVentures.TezPusher.Web.Hubs
9 | {
10 | public class TezosHub : Hub
11 | {
12 | private ILogger _log;
13 | private ITezosHistoryService _tezosHistoryService;
14 |
15 | public TezosHub(ILogger log, ITezosHistoryService tezosHistoryService)
16 | {
17 | _log = log;
18 | _tezosHistoryService = tezosHistoryService;
19 | }
20 |
21 | public async Task SendMessage(string method, string data)
22 | {
23 | await Clients.All.SendAsync(method, new object[] { data });
24 | }
25 |
26 | public async Task Subscribe(SubscribeModel model)
27 | {
28 | if (model.FromBlockLevel.HasValue)
29 | {
30 | await _tezosHistoryService.ProcessHistoryAsync(Clients.Caller, Context.ConnectionId, model);
31 | }
32 |
33 | if (model.BlockHeaders)
34 | {
35 | await Groups.AddToGroupAsync(Context.ConnectionId, "block_headers");
36 | }
37 |
38 | foreach (var address in model.TransactionAddresses)
39 | {
40 | await Groups.AddToGroupAsync(Context.ConnectionId, $"transactions_{address}");
41 | }
42 |
43 | foreach (var address in model.OriginationAddresses)
44 | {
45 | await Groups.AddToGroupAsync(Context.ConnectionId, $"originations_{address}");
46 | }
47 |
48 | foreach (var address in model.DelegationAddresses)
49 | {
50 | await Groups.AddToGroupAsync(Context.ConnectionId, $"delegations_{address}");
51 | }
52 | await Clients.Caller.SendAsync("subscribed",new object[] { model });
53 |
54 | if (model.FromBlockLevel != null)
55 | {
56 |
57 | }
58 | }
59 | public async Task Unsubscribe(SubscribeModel model)
60 | {
61 | foreach (var address in model.TransactionAddresses)
62 | {
63 | try
64 | {
65 | await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"transactions_{address}");
66 | }
67 | catch (Exception e)
68 | {
69 | _log.LogError(e,$"Unable to unsubscribe ConnectionId={Context.ConnectionId} from transactions_{address} group.");
70 | }
71 | }
72 |
73 | foreach (var address in model.OriginationAddresses)
74 | {
75 | try
76 | {
77 | await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"originations_{address}");
78 | }
79 | catch (Exception e)
80 | {
81 | _log.LogError(e, $"Unable to unsubscribe ConnectionId={Context.ConnectionId} from originations_{address} group.");
82 | }
83 | }
84 |
85 | foreach (var address in model.DelegationAddresses)
86 | {
87 | try
88 | {
89 | await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"delegations_{address}");
90 | }
91 | catch (Exception e)
92 | {
93 | _log.LogError(e, $"Unable to unsubscribe ConnectionId={Context.ConnectionId} from delegations_{address} group.");
94 | }
95 | }
96 |
97 | await Clients.Caller.SendAsync("unsubscribed", new object[] { model });
98 | }
99 |
100 | public async Task SendTransaction(string sourceAddress, string destinationAddress, PushMessage data)
101 | {
102 | await Clients.Groups(
103 | $"transactions_{sourceAddress}",
104 | $"transactions_{destinationAddress}",
105 | "transactions_all"
106 | ).
107 | SendAsync("transactions", new object[] { data });
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/app/signalr.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { HubConnection } from '@aspnet/signalr';
4 | import * as signalR from '@aspnet/signalr';
5 | import { Observable, from } from 'rxjs';
6 | import * as uuidv4 from 'uuid/v4';
7 | import { Subject } from 'rxjs';
8 |
9 | export interface PushMessage {
10 | block_header: Block;
11 | transaction: Transaction;
12 | origination: Origination;
13 | delegation: Delegation;
14 | }
15 |
16 | export interface Block {
17 | level: number;
18 | hash: string;
19 | timestamp: Date;
20 | validation_pass: number;
21 | proto: number;
22 | predecessor: string;
23 | operations_hash: string;
24 | fitness: string[];
25 | context: string;
26 | protocol_data: string;
27 | }
28 |
29 | export interface Operation {
30 | operation_hash: string;
31 | block_hash: string;
32 | block_level: number;
33 | timestamp: Date;
34 | }
35 |
36 | export interface OperationContent {
37 | source: string;
38 | fee: string;
39 | counter: string;
40 | gas_limit: string;
41 | storage_limit: string;
42 | }
43 |
44 | export interface Metadata {
45 | operation_result: OperationResults;
46 | }
47 |
48 | export interface OperationResults {
49 | originated_contracts: string[];
50 | }
51 |
52 |
53 | export interface Transaction extends Operation {
54 | transaction_content: TransactionContent;
55 | }
56 |
57 | export interface Origination extends Operation {
58 | origination_content: OriginationContent;
59 | }
60 |
61 | export interface Delegation extends Operation {
62 | delegation_content: DelegationContent;
63 | }
64 |
65 | export interface TransactionContent extends OperationContent {
66 | destination: string;
67 | amount: string;
68 | }
69 |
70 | export interface OriginationContent extends OperationContent {
71 | balance: string;
72 | amount: string;
73 | metadata: Metadata;
74 | }
75 |
76 | export interface DelegationContent extends OperationContent {
77 | delegate: string;
78 | }
79 |
80 | export interface Subscription {
81 | transactionAddresses: string[];
82 | delegationAddresses: string[];
83 | originationAddresses: string[];
84 | fromBlockLevel?: number;
85 | blockHeaders: boolean;
86 | }
87 |
88 | @Injectable()
89 | export class SignalRService {
90 |
91 | /*
92 | ***
93 | *** THIS NEEDS TO BE CONFIGURED PER YOUR ENVIRONMENT! ***
94 | ***
95 | */
96 | private readonly _baseUrl: string = 'https://localhost:44333';
97 |
98 | private hubConnection: HubConnection;
99 | blocks: Subject = new Subject();
100 | transactions: Subject = new Subject();
101 | originations: Subject = new Subject();
102 | delegations: Subject = new Subject();
103 |
104 | constructor() { }
105 |
106 | private subscribeToAll(model: Subscription): Observable {
107 | return from(this.hubConnection.send('subscribe', model));
108 | }
109 |
110 | private connect(): Observable {
111 | this.hubConnection = new signalR.HubConnectionBuilder()
112 | .withUrl(`${this._baseUrl}/tezosHub`)
113 | .configureLogging(signalR.LogLevel.Information)
114 | .build();
115 |
116 | return from(this.hubConnection.start());
117 | }
118 |
119 | init() {
120 | console.log(`initializing SignalRService...`);
121 | this.connect().subscribe(() => {
122 | console.log(`subscribing to all transactions, originations and delegations`);
123 | const model = {
124 | transactionAddresses: ['all'],
125 | delegationAddresses: ['all'],
126 | originationAddresses: ['all'],
127 | fromBlockLevel: 744190,
128 | blockHeaders: true
129 | };
130 | this.subscribeToAll(model)
131 | .subscribe(() => {
132 | console.log(`subscribed to all transactions, originations and delegations`);
133 | });
134 |
135 | this.hubConnection.on('block_headers', (data: PushMessage) => {
136 | this.blocks.next(data.block_header);
137 | });
138 |
139 | this.hubConnection.on('transactions', (data: PushMessage) => {
140 | this.transactions.next(data.transaction);
141 | });
142 |
143 | this.hubConnection.on('originations', (data: PushMessage) => {
144 | this.originations.next(data.origination);
145 | });
146 |
147 | this.hubConnection.on('delegations', (data: PushMessage) => {
148 | this.delegations.next(data.delegation);
149 | });
150 | });
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Function/SubscribeFunctions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Http;
6 | using Microsoft.Azure.WebJobs;
7 | using Microsoft.Azure.WebJobs.Extensions.Http;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.Azure.WebJobs.Extensions.SignalRService;
10 | using System.Threading.Tasks;
11 | using System.Web.Http;
12 | using AgileVentures.TezPusher.Model.PushEntities;
13 | using Microsoft.AspNetCore.Mvc;
14 | using Microsoft.Extensions.Logging;
15 | using Newtonsoft.Json;
16 |
17 | namespace AgileVentures.TezPusher.Function
18 | {
19 | public static class SubscribeFunctions
20 | {
21 | [FunctionName("subscribe")]
22 | public static async Task Subscribe(
23 | [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
24 | [SignalR(HubName = "broadcast")]
25 | IAsyncCollector signalRGroupActions,
26 | ILogger log)
27 | {
28 | try
29 | {
30 | var content = await req.Content.ReadAsStringAsync();
31 | if (string.IsNullOrEmpty(content))
32 | {
33 | log.LogError("Payload was null or empty");
34 | return new BadRequestResult();
35 | }
36 |
37 | var model = JsonConvert.DeserializeObject(content);
38 |
39 | var tasks = new List();
40 | tasks.AddRange(SubscribeAddresses(model.TransactionAddresses, "transactions_", signalRGroupActions, model.UserId));
41 | tasks.AddRange(SubscribeAddresses(model.OriginationAddresses, "originations_", signalRGroupActions, model.UserId));
42 | tasks.AddRange(SubscribeAddresses(model.DelegationAddresses, "delegations_", signalRGroupActions, model.UserId));
43 |
44 | await Task.WhenAll(tasks.ToArray());
45 | return new OkResult();
46 |
47 | }
48 | catch (Exception e)
49 | {
50 | log.LogError(e, "Error during subscribe", req);
51 | return new BadRequestResult();
52 | }
53 | }
54 |
55 | [FunctionName("unsubscribe")]
56 | public static async Task Unsubscribe(
57 | [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
58 | [SignalR(HubName = "broadcast")]
59 | IAsyncCollector signalRGroupActions,
60 | ILogger log)
61 | {
62 | try
63 | {
64 | var requestBody = new StreamReader(req.Body).ReadToEnd();
65 |
66 | if (string.IsNullOrEmpty(requestBody))
67 | {
68 | log.LogError("Payload was null or empty");
69 | return new BadRequestResult();
70 | }
71 |
72 | var model = JsonConvert.DeserializeObject(requestBody);
73 |
74 | var tasks = new List();
75 | tasks.AddRange(UnsubscribeAddresses(model.TransactionAddresses, "transactions_", signalRGroupActions, model.UserId));
76 | tasks.AddRange(UnsubscribeAddresses(model.OriginationAddresses, "originations_", signalRGroupActions, model.UserId));
77 | tasks.AddRange(UnsubscribeAddresses(model.DelegationAddresses, "delegations_", signalRGroupActions, model.UserId));
78 |
79 | await Task.WhenAll(tasks.ToArray());
80 | return new OkResult();
81 |
82 | }
83 | catch (Exception e)
84 | {
85 | log.LogError(e, "Error during unsubscribe", req);
86 | return new BadRequestResult();
87 | }
88 | }
89 |
90 | private static List SubscribeAddresses(List model, string prefix,
91 | IAsyncCollector signalRGroupActions, string userId)
92 | {
93 | if (model == null) return new List();
94 |
95 | var tasks = new List();
96 | foreach (var address in model)
97 | {
98 | tasks.Add(signalRGroupActions.AddAsync(
99 | new SignalRGroupAction
100 | {
101 | UserId = userId,
102 | GroupName = $"{prefix}{address}",
103 | Action = GroupAction.Add
104 | }));
105 | }
106 |
107 | return tasks;
108 | }
109 |
110 | private static List UnsubscribeAddresses(List model, string prefix,
111 | IAsyncCollector signalRGroupActions, string userId)
112 | {
113 | if (model == null) return new List();
114 |
115 | var tasks = new List();
116 | foreach (var address in model)
117 | {
118 | tasks.Add(signalRGroupActions.AddAsync(
119 | new SignalRGroupAction
120 | {
121 | UserId = userId,
122 | GroupName = $"{prefix}{address}",
123 | Action = GroupAction.Remove
124 | }));
125 | }
126 |
127 | return tasks;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient/src/app/signalr.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { HubConnection } from '@aspnet/signalr';
4 | import * as signalR from '@aspnet/signalr';
5 | import { Observable } from 'rxjs';
6 | import { SignalRConnectionInfo } from './signalr-connection-info.model';
7 | import * as uuidv4 from 'uuid/v4';
8 | import { Subject } from 'rxjs';
9 |
10 | export interface PushMessage {
11 | block_header: Block;
12 | transaction: Transaction;
13 | origination: Origination;
14 | delegation: Delegation;
15 | }
16 |
17 | export interface Block {
18 | level: number;
19 | hash: string;
20 | timestamp: Date;
21 | validation_pass: number;
22 | proto: number;
23 | predecessor: string;
24 | operations_hash: string;
25 | fitness: string[];
26 | context: string;
27 | protocol_data: string;
28 | }
29 |
30 | export interface Operation {
31 | operation_hash: string;
32 | block_hash: string;
33 | block_level: number;
34 | timestamp: Date;
35 | }
36 |
37 | export interface OperationContent {
38 | source: string;
39 | fee: string;
40 | counter: string;
41 | gas_limit: string;
42 | storage_limit: string;
43 | }
44 |
45 | export interface Metadata {
46 | operation_result: OperationResults;
47 | }
48 |
49 | export interface OperationResults {
50 | originated_contracts: string[];
51 | }
52 |
53 |
54 | export interface Transaction extends Operation {
55 | transaction_content: TransactionContent;
56 | }
57 |
58 | export interface Origination extends Operation {
59 | origination_content: OriginationContent;
60 | }
61 |
62 | export interface Delegation extends Operation {
63 | delegation_content: DelegationContent;
64 | }
65 |
66 | export interface TransactionContent extends OperationContent {
67 | destination: string;
68 | amount: string;
69 | }
70 |
71 | export interface OriginationContent extends OperationContent {
72 | balance: string;
73 | amount: string;
74 | metadata: Metadata;
75 | }
76 |
77 | export interface DelegationContent extends OperationContent {
78 | delegate: string;
79 | }
80 |
81 | export interface Subscription {
82 | userId: string;
83 | transactionAddresses: string[];
84 | delegationAddresses: string[];
85 | originationAddresses: string[];
86 | }
87 |
88 | @Injectable()
89 | export class SignalRService {
90 |
91 | private readonly _http: HttpClient;
92 | private baseUrl: string;
93 | private hubConnection: HubConnection;
94 | blocks: Subject = new Subject();
95 | transactions: Subject = new Subject();
96 | originations: Subject = new Subject();
97 | delegations: Subject = new Subject();
98 | userId: string;
99 |
100 | constructor(http: HttpClient) {
101 | this._http = http;
102 | this.userId = uuidv4();
103 | }
104 |
105 | private getConnectionInfo(): Observable {
106 | const requestUrl = `${this.baseUrl}negotiate`;
107 | return this._http.get(requestUrl,
108 | {
109 | headers: {
110 | 'x-tezos-live-userid': this.userId
111 | }
112 | });
113 | }
114 |
115 | private subscribeToAll(model: Subscription): Observable {
116 | const requestUrl = `${this.baseUrl}subscribe`;
117 | const httpOptions = {
118 | headers: new HttpHeaders({
119 | 'Content-Type': 'application/json',
120 | })
121 | };
122 | return this._http.post(requestUrl, model, httpOptions);
123 | }
124 |
125 | init(endpointUrl: string) {
126 | console.log(`initializing SignalRService...`);
127 | this.baseUrl = endpointUrl;
128 | this.getConnectionInfo().subscribe(info => {
129 | console.log(`received info for endpoint ${info.url}`);
130 | const options = {
131 | accessTokenFactory: () => info.accessToken
132 | };
133 |
134 | console.log(`subscribing to all transactions, originations and delegations`);
135 | const model = {
136 | userId: this.userId,
137 | transactionAddresses: ['all'],
138 | delegationAddresses: ['all'],
139 | originationAddresses: ['all']
140 | };
141 | this.subscribeToAll(model)
142 | .subscribe(() => {
143 | console.log(`subscribed to all transactions, originations and delegations`);
144 | });
145 |
146 | this.hubConnection = new signalR.HubConnectionBuilder()
147 | .withUrl(info.url, options)
148 | .configureLogging(signalR.LogLevel.Information)
149 | .build();
150 |
151 | this.hubConnection.start().catch(err => console.error(err.toString()));
152 |
153 | this.hubConnection.on('block_headers', (data: PushMessage) => {
154 | this.blocks.next(data.block_header);
155 | });
156 |
157 | this.hubConnection.on('transactions', (data: PushMessage) => {
158 | this.transactions.next(data.transaction);
159 | });
160 |
161 | this.hubConnection.on('originations', (data: PushMessage) => {
162 | this.originations.next(data.origination);
163 | });
164 |
165 | this.hubConnection.on('delegations', (data: PushMessage) => {
166 | this.delegations.next(data.delegation);
167 | });
168 | });
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Services/PushService.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using AgileVentures.TezPusher.Model.Constants;
4 | using AgileVentures.TezPusher.Model.Contracts;
5 | using AgileVentures.TezPusher.Model.PushEntities;
6 | using AgileVentures.TezPusher.Model.RpcEntities;
7 | using AgileVentures.TezPusher.Web.Hubs;
8 | using Microsoft.AspNetCore.SignalR;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace AgileVentures.TezPusher.Web.Services
12 | {
13 | public interface IPushService
14 | {
15 | Task PushBlockHeader(HeadModel model);
16 | Task PushOperations(BlockRpcEntity blockModel);
17 | }
18 |
19 | public class PushService : IPushService
20 | {
21 | private readonly IHubContext _hubContext;
22 | private readonly ILogger _log;
23 |
24 | public PushService(IHubContext hubContext, ILogger log)
25 | {
26 | _hubContext = hubContext;
27 | _log = log;
28 | }
29 |
30 | public async Task PushBlockHeader(HeadModel model)
31 | {
32 | //await _hubContext.Clients.All.SendAsync("block_headers", new PushMessage(model));
33 | await _hubContext.Clients.Groups("block_headers").SendAsync("block_headers", new PushMessage(model));
34 | _log.LogDebug($"Processing block {model.level}. Block header messages have been sent.");
35 | }
36 |
37 | public async Task PushOperations(BlockRpcEntity model)
38 | {
39 | var operations = model.GetOperations();
40 | await PushTransactions(model, operations);
41 | await PushDelegations(model, operations);
42 | await PushOriginations(model, operations);
43 | _log.LogDebug($"Processing block {model.header.level}. All operation messages have been sent.");
44 | }
45 |
46 | private async Task PushTransactions(BlockRpcEntity model, BlockOperations operations)
47 | {
48 | foreach (var transaction in operations.Transactions)
49 | {
50 | var content = transaction.contents.Where(c =>
51 | c.kind == TezosBlockOperationConstants.Transaction && c.metadata.operation_result.status ==
52 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
53 | foreach (var transactionContent in content)
54 | {
55 | // Babylon upgrade - KT1 transactions are smart contract operations
56 | var txSource = transactionContent.GetTransactionSource();
57 | var txDestination = transactionContent.GetTransactionDestination();
58 | var txContent = transactionContent.GetInternalTransactionContent();
59 |
60 | var message = new PushMessage(new TransactionModel(model, transaction, txContent));
61 | await _hubContext.Clients.Groups(
62 | $"transactions_{txSource}",
63 | $"transactions_{txDestination}",
64 | "transactions_all"
65 | )
66 | .SendAsync("transactions", message);
67 |
68 | _log.LogDebug($"Block {model.header.level} | " +
69 | $"Operation hash {transaction.hash} has been sent to the following groups [transactions_{transactionContent.source}, transactions_{transactionContent.destination}, transactions_all]");
70 | }
71 | }
72 | }
73 |
74 | private async Task PushDelegations(BlockRpcEntity model, BlockOperations operations)
75 | {
76 | foreach (var delegation in operations.Delegations)
77 | {
78 | var content = delegation.contents.Where(c =>
79 | c.kind == TezosBlockOperationConstants.Delegation && c.metadata.operation_result.status ==
80 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
81 | foreach (var delegationContent in content)
82 | {
83 | var message = new PushMessage(new DelegationModel(model, delegation, delegationContent));
84 | await _hubContext.Clients.Groups(
85 | $"delegations_{delegationContent.source}",
86 | $"delegations_{delegationContent.@delegate}",
87 | "delegations_all"
88 | )
89 | .SendAsync("delegations", message);
90 |
91 | _log.LogDebug($"Block {model.header.level} | " +
92 | $"Operation hash {delegation.hash} has been sent to the following groups [delegations_{delegationContent.source}, delegations_{delegationContent.@delegate}, delegations_all]");
93 | }
94 | }
95 | }
96 |
97 | private async Task PushOriginations(BlockRpcEntity model, BlockOperations operations)
98 | {
99 | foreach (var originations in operations.Originations)
100 | {
101 | var content = originations.contents.Where(c =>
102 | c.kind == TezosBlockOperationConstants.Origination && c.metadata.operation_result.status ==
103 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
104 | foreach (var originationContent in content)
105 | {
106 | var message = new PushMessage(new OriginationModel(model, originations, originationContent));
107 | await _hubContext.Clients.Groups(
108 | $"originations_{originationContent.source}",
109 | "originations_all"
110 | )
111 | .SendAsync("originations", message);
112 |
113 | _log.LogDebug($"Block {model.header.level} | " +
114 | $"Operation hash {originations.hash} has been sent to the following groups [originations_{originationContent.source}, originations_all]");
115 | }
116 | }
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.ConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net.Http;
4 | using System.Net.Http.Headers;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using AgileVentures.TezPusher.Model.Configuration;
8 | using AgileVentures.TezPusher.Model.RpcEntities;
9 | using Microsoft.Extensions.Configuration;
10 | using Newtonsoft.Json;
11 | using NLog;
12 | using NLog.Config;
13 |
14 | namespace AgileVentures.TezPusher.ConsoleApp
15 | {
16 | class Program
17 | {
18 | private static readonly HttpClient Client = new HttpClient(new HttpClientHandler
19 | {
20 | AllowAutoRedirect = false,
21 | MaxAutomaticRedirections = 20
22 | });
23 |
24 | private static Logger _log;
25 | private static AzureConfig _azureConfig;
26 | private static TezosConfig _tezosConfig;
27 | private static bool _keepRunning = true;
28 |
29 | private static string TezosMonitorUrl => $"{_tezosConfig.NodeUrl}/monitor/heads/main";
30 | private static string MessageUrl => $"{_azureConfig.AzureFunctionUrl}/api/message?code={_azureConfig.AzureFunctionKey}";
31 |
32 |
33 | static async Task Main(string[] args)
34 | {
35 | LogManager.Configuration = new XmlLoggingConfiguration($"{AppContext.BaseDirectory}nlog.config");
36 | _log = LogManager.GetLogger("AgileVentures.TezPusher.ConsoleApp");
37 | try
38 | {
39 | _log.Info("Starting Tezos Pusher");
40 | _log.Info("Press CTRL+C to exit the application.");
41 |
42 | GetConfiguration();
43 | _log.Info($"Configuration Loaded");
44 |
45 | RegisterGracefulShutdown();
46 | while (_keepRunning)
47 | {
48 | try
49 | {
50 | await MonitorChainHead();
51 | }
52 | catch (Exception e)
53 | {
54 | _log.Error(e);
55 | }
56 | }
57 | }
58 | catch (Exception e)
59 | {
60 | _log.Error(e, "Error occured!");
61 | throw;
62 | }
63 | finally
64 | {
65 | // Ensure to flush and stop internal timers/threads before application-exit(Avoid segmentation fault on Linux)
66 | LogManager.Shutdown();
67 | }
68 | }
69 |
70 | private static void RegisterGracefulShutdown()
71 | {
72 | Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
73 | {
74 | _log.Info("Stopping application gracefully...");
75 | e.Cancel = true;
76 | _keepRunning = false;
77 | };
78 |
79 | //Docker env.
80 | System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += context =>
81 | {
82 | _log.Info("Stopping application gracefully...");
83 | _keepRunning = false;
84 | };
85 | }
86 |
87 | private static void GetConfiguration()
88 | {
89 | var configuration = new ConfigurationBuilder()
90 | .SetBasePath(Directory.GetCurrentDirectory())
91 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
92 | .AddEnvironmentVariables()
93 | .Build();
94 |
95 | _azureConfig = configuration.GetSection("Azure").Get();
96 | _tezosConfig = configuration.GetSection("Tezos").Get();
97 |
98 | if (string.IsNullOrEmpty(_tezosConfig.NodeUrl))
99 | {
100 | throw new ArgumentException(
101 | $"Tezos:NodeUrl configuration is empty. Please provide a valid URL in appsettings.json or ENV variables.");
102 | }
103 |
104 | if (string.IsNullOrEmpty(_azureConfig.AzureFunctionUrl))
105 | {
106 | throw new ArgumentException(
107 | $"Azure:AzureFunctionUrl configuration is empty. Please provide a valid URL in appsettings.json or ENV variables.");
108 | }
109 |
110 | if (string.IsNullOrEmpty(_azureConfig.AzureFunctionKey))
111 | {
112 | throw new ArgumentException(
113 | $"Azure:AzureFunctionKey configuration is empty. Please provide a valid key in appsettings.json or ENV variables.");
114 | }
115 | }
116 |
117 | private static async Task MonitorChainHead()
118 | {
119 |
120 | Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
121 | var request = new HttpRequestMessage(HttpMethod.Get, TezosMonitorUrl);
122 | var result = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None);
123 | var stream = await result.Content.ReadAsStreamAsync();
124 | var sr = new StreamReader(stream);
125 | string line;
126 |
127 | _log.Info("Started Tezos Node Monitoring");
128 | while ((line = sr.ReadLine()) != null && _keepRunning)
129 | {
130 | try
131 | {
132 | var head = JsonConvert.DeserializeObject(line);
133 |
134 | var blockString = await Client.GetStringAsync(GetBlockUrl(head.hash));
135 |
136 | await Client.PostAsync(MessageUrl, new StringContent(blockString));
137 |
138 | _log.Info($"Block {head.level} has been sent for processing.");
139 | _log.Trace(line);
140 | }
141 | catch (Exception ex)
142 | {
143 | _log.Error(ex, $"Failed to send the block for processing.");
144 | }
145 | }
146 | }
147 |
148 | private static string GetBlockUrl(string hash)
149 | {
150 | return $"{_tezosConfig.NodeUrl}{string.Format("/chains/main/blocks/{0}", hash)}";
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Web/Services/PushHistoryService.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using AgileVentures.TezPusher.Model.Constants;
4 | using AgileVentures.TezPusher.Model.Contracts;
5 | using AgileVentures.TezPusher.Model.PushEntities;
6 | using AgileVentures.TezPusher.Model.RpcEntities;
7 | using Microsoft.AspNetCore.SignalR;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace AgileVentures.TezPusher.Web.Services
11 | {
12 | public interface IPushHistoryService
13 | {
14 | Task PushBlockHeader(IClientProxy clientsCaller, HeadModel model);
15 | Task PushOperations(IClientProxy clientsCaller, BlockRpcEntity blockModel, SubscribeModel model);
16 | }
17 |
18 | public class PushHistoryService : IPushHistoryService
19 | {
20 | private readonly ILogger _log;
21 |
22 | public PushHistoryService(ILogger log)
23 | {
24 | _log = log;
25 | }
26 |
27 | public async Task PushBlockHeader(IClientProxy clientsCaller, HeadModel model)
28 | {
29 | await clientsCaller.SendAsync("block_headers", new PushMessage(model));
30 | _log.LogDebug($"Processing history block {model.level}. Block header message have been sent.");
31 | }
32 |
33 | public async Task PushOperations(IClientProxy clientsCaller, BlockRpcEntity blockModel, SubscribeModel subscribeModel)
34 | {
35 | var operations = blockModel.GetOperations();
36 | await PushTransactions(clientsCaller, subscribeModel, blockModel, operations);
37 | await PushDelegations(clientsCaller, subscribeModel, blockModel, operations);
38 | await PushOriginations(clientsCaller, subscribeModel, blockModel, operations);
39 | _log.LogDebug($"Processing history block {blockModel.header.level}. All operation messages have been sent.");
40 | }
41 |
42 | private async Task PushTransactions(IClientProxy clientsCaller, SubscribeModel subscribeModel,
43 | BlockRpcEntity model,
44 | BlockOperations operations)
45 | {
46 | foreach (var transaction in operations.Transactions)
47 | {
48 | var content = transaction.contents.Where(c =>
49 | c.kind == TezosBlockOperationConstants.Transaction && c.metadata.operation_result.status ==
50 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
51 | foreach (var transactionContent in content)
52 | {
53 | // Babylon upgrade - KT1 transactions are smart contract operations
54 | var txSource = transactionContent.GetTransactionSource();
55 | var txDestination = transactionContent.GetTransactionDestination();
56 | var txContent = transactionContent.GetInternalTransactionContent();
57 | if (subscribeModel.TransactionAddresses.Contains("all") ||
58 | subscribeModel.TransactionAddresses.Contains(txSource) ||
59 | subscribeModel.TransactionAddresses.Contains(txDestination))
60 | {
61 | var message = new PushMessage(new TransactionModel(model, transaction, txContent));
62 | await clientsCaller.SendAsync("transactions", message);
63 | _log.LogDebug($"History Block {model.header.level} | " +
64 | $"Operation hash {transaction.hash} has been sent. TxSource={txSource}, TxDestination={txDestination}");
65 | }
66 | }
67 | }
68 | }
69 |
70 | private async Task PushDelegations(IClientProxy clientsCaller, SubscribeModel subscribeModel,
71 | BlockRpcEntity model, BlockOperations operations)
72 | {
73 | foreach (var delegation in operations.Delegations)
74 | {
75 | var content = delegation.contents.Where(c =>
76 | c.kind == TezosBlockOperationConstants.Delegation && c.metadata.operation_result.status ==
77 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
78 | foreach (var delegationContent in content)
79 | {
80 | if (subscribeModel.DelegationAddresses.Contains("all") ||
81 | subscribeModel.DelegationAddresses.Contains(delegationContent.source) ||
82 | subscribeModel.DelegationAddresses.Contains(delegationContent.@delegate))
83 | {
84 | var message = new PushMessage(new DelegationModel(model, delegation, delegationContent));
85 | await clientsCaller.SendAsync("delegations", message);
86 |
87 | _log.LogDebug($"History block {model.header.level} | " +
88 | $"Operation hash {delegation.hash} has been sent. DelegationSource={delegationContent.source}, DelegationDestination={delegationContent.@delegate}");
89 | }
90 | }
91 | }
92 | }
93 |
94 | private async Task PushOriginations(IClientProxy clientsCaller, SubscribeModel subscribeModel,
95 | BlockRpcEntity model,
96 | BlockOperations operations)
97 | {
98 | foreach (var originations in operations.Originations)
99 | {
100 | var content = originations.contents.Where(c =>
101 | c.kind == TezosBlockOperationConstants.Origination && c.metadata.operation_result.status ==
102 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
103 | foreach (var originationContent in content)
104 | {
105 | if (subscribeModel.OriginationAddresses.Contains("all") ||
106 | subscribeModel.OriginationAddresses.Contains(originationContent.source))
107 | {
108 | var message = new PushMessage(new OriginationModel(model, originations, originationContent));
109 | await clientsCaller.SendAsync("originations", message);
110 |
111 | _log.LogDebug($"History block {model.header.level} | " +
112 | $"Operation hash {originations.hash} has been sent. OriginationSource={originationContent.source}");
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | #Visual Studio Code
14 | .vscode/*
15 | !.vscode/settings.json
16 | !.vscode/tasks.json
17 | !.vscode/launch.json
18 | !.vscode/extensions.json
19 |
20 | # User-specific files (MonoDevelop/Xamarin Studio)
21 | *.userprefs
22 |
23 | # Mono auto generated files
24 | mono_crash.*
25 |
26 | # Build results
27 | [Dd]ebug/
28 | [Dd]ebugPublic/
29 | [Rr]elease/
30 | [Rr]eleases/
31 | x64/
32 | x86/
33 | [Aa][Rr][Mm]/
34 | [Aa][Rr][Mm]64/
35 | bld/
36 | [Bb]in/
37 | [Oo]bj/
38 | [Ll]og/
39 |
40 | # Visual Studio 2015/2017 cache/options directory
41 | .vs/
42 | # Uncomment if you have tasks that create the project's static files in wwwroot
43 | #wwwroot/
44 |
45 | # Visual Studio 2017 auto generated files
46 | Generated\ Files/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUnit
53 | *.VisualState.xml
54 | TestResult.xml
55 | nunit-*.xml
56 |
57 | # Build Results of an ATL Project
58 | [Dd]ebugPS/
59 | [Rr]eleasePS/
60 | dlldata.c
61 |
62 | # Benchmark Results
63 | BenchmarkDotNet.Artifacts/
64 |
65 | # .NET Core
66 | project.lock.json
67 | project.fragment.lock.json
68 | artifacts/
69 |
70 | # StyleCop
71 | StyleCopReport.xml
72 |
73 | # Files built by Visual Studio
74 | *_i.c
75 | *_p.c
76 | *_h.h
77 | *.ilk
78 | *.meta
79 | *.obj
80 | *.iobj
81 | *.pch
82 | *.pdb
83 | *.ipdb
84 | *.pgc
85 | *.pgd
86 | *.rsp
87 | *.sbr
88 | *.tlb
89 | *.tli
90 | *.tlh
91 | *.tmp
92 | *.tmp_proj
93 | *_wpftmp.csproj
94 | *.log
95 | *.vspscc
96 | *.vssscc
97 | .builds
98 | *.pidb
99 | *.svclog
100 | *.scc
101 |
102 | # Chutzpah Test files
103 | _Chutzpah*
104 |
105 | # Visual C++ cache files
106 | ipch/
107 | *.aps
108 | *.ncb
109 | *.opendb
110 | *.opensdf
111 | *.sdf
112 | *.cachefile
113 | *.VC.db
114 | *.VC.VC.opendb
115 |
116 | # Visual Studio profiler
117 | *.psess
118 | *.vsp
119 | *.vspx
120 | *.sap
121 |
122 | # Visual Studio Trace Files
123 | *.e2e
124 |
125 | # TFS 2012 Local Workspace
126 | $tf/
127 |
128 | # Guidance Automation Toolkit
129 | *.gpState
130 |
131 | # ReSharper is a .NET coding add-in
132 | _ReSharper*/
133 | *.[Rr]e[Ss]harper
134 | *.DotSettings.user
135 |
136 | # JustCode is a .NET coding add-in
137 | .JustCode
138 |
139 | # TeamCity is a build add-in
140 | _TeamCity*
141 |
142 | # DotCover is a Code Coverage Tool
143 | *.dotCover
144 |
145 | # AxoCover is a Code Coverage Tool
146 | .axoCover/*
147 | !.axoCover/settings.json
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.Function/MessageFunction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.Http;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.Azure.WebJobs.Extensions.SignalRService;
8 | using System.Threading.Tasks;
9 | using AgileVentures.TezPusher.Model.Constants;
10 | using AgileVentures.TezPusher.Model.Contracts;
11 | using AgileVentures.TezPusher.Model.PushEntities;
12 | using AgileVentures.TezPusher.Model.RpcEntities;
13 | using Microsoft.Extensions.Logging;
14 | using Newtonsoft.Json;
15 |
16 | namespace AgileVentures.TezPusher.Function
17 | {
18 | public static class MessageFunction
19 | {
20 | [FunctionName("message")]
21 | public static Task Message(
22 | [HttpTrigger(AuthorizationLevel.Function, "post")]HttpRequest req,
23 | [SignalR(HubName = "broadcast")]IAsyncCollector signalRMessages,
24 | ILogger log)
25 | {
26 | try
27 | {
28 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings
29 | {
30 | NullValueHandling = NullValueHandling.Ignore
31 | };
32 |
33 | var requestBody = new StreamReader(req.Body).ReadToEnd();
34 |
35 | if (string.IsNullOrEmpty(requestBody))
36 | {
37 | log.LogError("Payload was null or empty");
38 | return Task.CompletedTask;
39 | }
40 | log.LogTrace($"Message with payload {requestBody}");
41 |
42 | var model = JsonConvert.DeserializeObject(requestBody);
43 | log.LogInformation($"Message with block level {model.header.level}");
44 |
45 | var blockHeader = new HeadModel(model);
46 | signalRMessages.AddAsync(new SignalRMessage
47 | {
48 | Target = "block_headers",
49 | Arguments = new object[] { new PushMessage(blockHeader) }
50 | });
51 |
52 | var operations = model.GetOperations();
53 | PushTransactions(signalRMessages, operations, model);
54 | PushDelegations(signalRMessages, operations, model);
55 | PushOriginations(signalRMessages, operations, model);
56 | }
57 | catch (Exception e)
58 | {
59 | log.LogError(e, "Error during running message function");
60 | }
61 |
62 | return Task.CompletedTask;
63 | }
64 |
65 | private static void PushOriginations(IAsyncCollector signalRMessages, BlockOperations operations, BlockRpcEntity model)
66 | {
67 | foreach (var origination in operations.Originations)
68 | {
69 | var content = origination.contents.Where(c =>
70 | c.kind == TezosBlockOperationConstants.Origination && c.metadata.operation_result.status ==
71 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
72 | foreach (var originationContent in content)
73 | {
74 | signalRMessages.AddAsync(new SignalRMessage
75 | {
76 | GroupName = $"originations_{originationContent.source}",
77 | Arguments = new object[] { new PushMessage(new OriginationModel(model, origination, originationContent)) },
78 | Target = "originations"
79 | });
80 | signalRMessages.AddAsync(new SignalRMessage
81 | {
82 | GroupName = "originations_all",
83 | Arguments = new object[] { new PushMessage(new OriginationModel(model, origination, originationContent)) },
84 | Target = "originations"
85 | });
86 | }
87 | }
88 | }
89 |
90 | private static void PushDelegations(IAsyncCollector signalRMessages, BlockOperations operations, BlockRpcEntity model)
91 | {
92 | foreach (var delegation in operations.Delegations)
93 | {
94 | var content = delegation.contents.Where(c =>
95 | c.kind == TezosBlockOperationConstants.Delegation && c.metadata.operation_result.status ==
96 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
97 | foreach (var delegationContent in content)
98 | {
99 | signalRMessages.AddAsync(new SignalRMessage
100 | {
101 | GroupName = $"delegations_{delegationContent.source}",
102 | Arguments = new object[] { new PushMessage(new DelegationModel(model, delegation, delegationContent)) },
103 | Target = "delegations"
104 | });
105 | signalRMessages.AddAsync(new SignalRMessage
106 | {
107 | GroupName = $"delegations_{delegationContent.@delegate}",
108 | Arguments = new object[] { new PushMessage(new DelegationModel(model, delegation, delegationContent)) },
109 | Target = "delegations"
110 | });
111 | signalRMessages.AddAsync(new SignalRMessage
112 | {
113 | GroupName = "delegations_all",
114 | Arguments = new object[] { new PushMessage(new DelegationModel(model, delegation, delegationContent)) },
115 | Target = "delegations"
116 | });
117 | }
118 | }
119 | }
120 |
121 | private static void PushTransactions(IAsyncCollector signalRMessages, BlockOperations operations,
122 | BlockRpcEntity model)
123 | {
124 | foreach (var transaction in operations.Transactions)
125 | {
126 | var content = transaction.contents.Where(c =>
127 | c.kind == TezosBlockOperationConstants.Transaction && c.metadata.operation_result.status ==
128 | TezosBlockOperationConstants.OperationResultStatusApplied).ToList();
129 | foreach (var operationContent in content)
130 | {
131 | var transactionContent = (BlockTransactionContent)operationContent;
132 | // Babylon upgrade - KT1 transactions are smart contract operations
133 | var txSource = transactionContent.GetTransactionSource();
134 | var txDestination = transactionContent.GetTransactionDestination();
135 | var txContent = transactionContent.GetInternalTransactionContent();
136 |
137 | signalRMessages.AddAsync(new SignalRMessage
138 | {
139 | GroupName = $"transactions_{txSource}",
140 | Arguments = new object[] { new PushMessage(new TransactionModel(model, transaction, txContent)) },
141 | Target = "transactions"
142 | });
143 | signalRMessages.AddAsync(new SignalRMessage
144 | {
145 | GroupName = $"transactions_{txDestination}",
146 | Arguments = new object[] { new PushMessage(new TransactionModel(model, transaction, txContent)) },
147 | Target = "transactions"
148 | });
149 | signalRMessages.AddAsync(new SignalRMessage
150 | {
151 | GroupName = "transactions_all",
152 | Arguments = new object[] { new PushMessage(new TransactionModel(model, transaction, txContent)) },
153 | Target = "transactions"
154 | });
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TaaS \(Tezos as a Service\)
2 |
3 | ### About TaaS
4 |
5 | TaaS provides real-time updates to various applications based on the events happening on Tezos by leveraging SignalR \(WebSocket\).
6 |
7 | ### Documentation
8 |
9 | [https://docs.tezoslive.io/docs-welcome](https://docs.tezoslive.io/docs-welcome)
10 |
11 | ## Table of contents
12 |
13 | * [How to use](#how-to-use)
14 | * [Solution description](#solution-description)
15 | * [Sample Client Applications](#sample-client-applications)
16 |
17 | ## How to use
18 |
19 | ### Option \#1 - Running [Pusher.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Pusher.Web) in Docker
20 |
21 | Ready-to-use docker image is available from Docker Hub here: [https://hub.docker.com/r/tezoslive/agileventurestezpusherweb](https://hub.docker.com/r/tezoslive/agileventurestezpusherweb).
22 |
23 | You can start the container by using the following command
24 |
25 | ```text
26 | docker run --rm -it -p 80:80 \
27 | --env Tezos:NodeUrl="http://172.17.0.1:8732" \
28 | tezoslive/agileventurestezpusherweb
29 | ```
30 |
31 | This will expose port `80` to the host and set your Tezos Node RPC to `http://172.17.0.1:8732`.
32 |
33 | {% hint style="warning" %}
34 | **Do not forget to replace the NodeUrl per your environment!**
35 | {% endhint %}
36 |
37 | Please make sure to check the [documentation](https://docs.tezoslive.io/docs-getting-started/docs-using-docker) for additional information.
38 |
39 | #### Configuration needed
40 |
41 | Provide a configuration for `Pusher.Web` project in
42 |
43 | * the `ENV` variable `Tezos:NodeUrl` has to be set. Configured Tezos RPC endpoint ****must support following calls
44 | * `monitor/heads/main`
45 | * `/chains/main/blocks/{hash}`
46 |
47 | For client side instructions please see [Subscribing to events from the client - Option 1 or 2](./#i-am-using-option-1-or-2).
48 |
49 | ### Option \#2 - Running [Pusher.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Pusher.Web) as a standalone ASP.NET Core app
50 |
51 | Provide a configuration for `Pusher.Web` project in
52 |
53 | * `appsettings.json` file. You will need to fill in this value `"NodeUrl": ""` . Configured Tezos RPC endpoint must support following calls
54 | * `monitor/heads/main`
55 | * `/chains/main/blocks/{hash}`
56 |
57 | For client side instructions please see [Subscribing to events from the client - Option 1 or 2](./#i-am-using-option-1-or-2).
58 |
59 | ### Option \#3 - Using Azure Functions and TezPusher.ConsoleApp
60 |
61 | #### ConsoleApp Configuration
62 |
63 | Provide a configuration for `ConsoleApp` project in the `appsettings.json` file if you are running from compiled sources or `ENV` variables if you are running from Docker.
64 |
65 | {% hint style="warning" %}
66 | Be sure to configure the following keys correctly per your environment
67 |
68 | * `Tezos:NodeUrl` - Tezos RPC endpoint URL
69 | * `Azure:AzureFunctionUrl` - URL of your deployed function app
70 | * `Azure:AzureFunctionKey` - Access key for your message function of your deployed function app
71 | {% endhint %}
72 |
73 | #### Function App Configuration
74 |
75 | Provide a configuration for `Function` project in the `local.settings.json` file if you are running it locally or Azure Applications Settings if you are running in Azure. There is a pre-filled endpoint which is hosted on Azure Free plan, so it might be already above daily threshold. You can create a SignalR Service on Azure for free on [Azure](https://azure.microsoft.com/en-us/) and provide your own SignalR connection string.
76 |
77 | * `"AzureSignalRConnectionString": ""`
78 |
79 | For client side instructions please see [Subscribing to events from the client - Option 3 or 4](./#i-am-using-option-3-or-4).
80 |
81 | ### Option \#4 - Using the endpoint from [TezosLive.io](https://tezoslive.io) \(most convenient\)
82 |
83 | Sign in using your GitHub account on [TezosLive.io](https://tezoslive.io) and request your endpoint.
84 |
85 | {% hint style="info" %}
86 | You don't need to host anything on server side.
87 | {% endhint %}
88 |
89 | API is currently limited to
90 |
91 | * 20 000 messages per account per day \(1 message is counted for each 64kB in case message has more than 64kB\)
92 | * 20 concurrent connection per account
93 |
94 | Please make sure to check the [documentation](https://docs.tezoslive.io/docs-getting-started/docs-using-tezoslive.io-endpoint) for additional information.
95 |
96 | For client side instructions please see [Subscribing to events from the client - Option 3 or 4](./#i-am-using-option-3-or-4).
97 |
98 | If you need more messages or concurrent connections please contact us _hello AT tezoslive.io._
99 |
100 | ## Subscribing to events from the client
101 |
102 | ### I am using option \#1 or \#2
103 |
104 | You can connect to the hub for example like this \(see `signalr.service.ts`\)
105 |
106 | ```typescript
107 | private connect(): Observable {
108 | this.hubConnection = new signalR.HubConnectionBuilder()
109 | .withUrl(`${this._baseUrl}/tezosHub`)
110 | .configureLogging(signalR.LogLevel.Information)
111 | .build();
112 | return from(this.hubConnection.start());
113 | }
114 | ```
115 |
116 | You can then subscribe to transactions like this.
117 |
118 | ```typescript
119 | this.hubConnection.send("subscribe", {
120 | transactionAddresses: ['all'],
121 | delegationAddresses: ['all'],
122 | originationAddresses: ['all']
123 | });
124 | ```
125 |
126 | Note: `transactionAddresses`, `delegationAddresses` and `originationAdresses` are `string[]`.
127 |
128 | {% hint style="info" %}
129 | Specifying **'all'** will subscribe the client to all transactions/delegations/originations respectively.
130 | {% endhint %}
131 |
132 | For reference please take a look at [AgileVentures.TezPusher.SampleClient.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web) specifically [`signalr.service.ts`](https://github.com/agile-ventures/TaaS/blob/84fe386b38f5e488a194a2aa531b109c7dc435d6/AgileVentures.TezPusher.SampleClient.Web/src/app/signalr.service.ts#L65).
133 |
134 | ### I am using option \#3 or \#4
135 |
136 | You will need to provide a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) in a custom HTTP header named `x-tezos-live-userid` to identify a client during the initial call to `negotiate` endpoint. In the sample client application we are using the [npm uuid package](https://www.npmjs.com/package/uuid) to generate random UUIDs.
137 |
138 | You can see how the subscription to all transactions is being made by looking at the `signalr.service.ts` [here](https://github.com/agile-ventures/TaaS/blob/master/AgileVentures.TezPusher.SampleClient/src/app/signalr.service.ts) by making a `POST` request to `subscribe` endpoint with the following parameters
139 |
140 | * userId is `string` - this is the UUID you have used for the `negotiate` call
141 | * `transactionAddresses`, `delegationAddresses` and `originationAddresses`are `string[]` - this is the array of the addresses that you want to subscribe to. You can subscribe to all addresses by sending `['all']`
142 |
143 | You can also subscribe only to a subset of addresses, that you are interested in by providing them as a parameter to `subscribe` call. You need to provide the generated UUID that you used in the `negotiate` call along with the array of the addresses.
144 |
145 | For reference please take a look at [AgileVentures.TezPusher.SampleClient](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient).
146 |
147 | Or you can check out deployed version of this app available here [https://client-staging.tezoslive.io/](https://client-staging.tezoslive.io/).
148 |
149 | ## Solution Description
150 |
151 | Solution consists of several projects described bellow
152 |
153 | * [AgileVentures.TezPusher.Function](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Function) Azure Function getting the updates from Pusher.ConsoleApp and sending the updates to SignalR hub.
154 | * [AgileVentures.TezPusher.Model](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Model) Simple Model for the updates. This will be extended heavily based on the different subscriptions.
155 | * [AgileVentures.TezPusher.Pusher.ConsoleApp](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Pusher.ConsoleApp) Small Console Application in .NET Core used to monitor Tezos Node and push updates to the Azure Function.
156 | * [AgileVentures.TezPusher.Pusher.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.Pusher.Web) ASP.NET Core Application, that monitors Tezos Node and also provides updates to clients through SignalR hub over WebSocket transport.
157 |
158 | **Docker supported!** To try-out docker version you can also get it from Docker Hub here [https://hub.docker.com/r/tezoslive/agileventurestezpusherweb](https://hub.docker.com/r/tezoslive/agileventurestezpusherweb). See instructions for [Option \#1](./#option-1---running-pusherweb-in-docker-most-convenient-at-the-moment).
159 |
160 | * [AgileVentures.TezPusher.SampleClient](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient) Sample Client application written in Angular consuming the updates provided by the Azure SignalR hub.
161 | * [AgileVentures.TezPusher.SampleClient.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web) Sample Client application written in Angular consuming the updates provided by the ASP.NET Core SignalR hub.
162 |
163 | ### Sample Client Applications
164 |
165 | * For [Option \#1](./#option-1---running-pusherweb-in-docker-most-convenient-at-the-moment) & [Option \#2](./#option-2---running-pusherweb-as-a-standalone-aspnet-core-app) - [AgileVentures.TezPusher.SampleClient.Web](https://github.com/agile-ventures/TaaS/tree/master/AgileVentures.TezPusher.SampleClient.Web)
166 | * For [Option \#3](./#option-3---using-azure-functions-and-tezpusherconsoleapp) & [Option \#4](./#option-4---using-the-endpoint-from-tezosliveio) at [https://client-staging.tezoslive.io/](https://client-staging.tezoslive.io/)
167 |
168 |
--------------------------------------------------------------------------------
/AgileVentures.TezPusher.SampleClient.Web/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Tezos as a Service [STAGING] - Client Side Example
3 |
4 | This application is using websocket connection available from TAAS (Tezos as a Service). SignalR
5 | (WebSocket) Connection is available at https://taas-staging.agile-ventures.com/api/negotiate. You can see
6 | the sample subscribing to all transactions, originations and delegations in a signalr.service.ts file.
7 |
8 |
9 | We will offer more subscriptions for real-time push updates based on the address, minimum transacted amount,
10 | etc.
11 |