├── .dockerignore ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── AgileVentures.TezPusher.ConsoleApp ├── AgileVentures.TezPusher.ConsoleApp.csproj ├── Dockerfile ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.json └── nlog.config ├── AgileVentures.TezPusher.Function ├── AgileVentures.TezPusher.Function.csproj ├── MessageFunction.cs ├── NegotiateFunction.cs ├── SubscribeFunctions.cs ├── host.json └── local.settings.json ├── AgileVentures.TezPusher.Model ├── AgileVentures.TezPusher.Model.csproj ├── Configuration │ └── ConsoleAppConfig.cs ├── Constants │ ├── PushMessageTypes.cs │ └── TezosBlockOperationConstants.cs ├── Contracts │ └── BlockOperations.cs ├── Interfaces │ ├── IBlockLevelRpcEntity.cs │ └── IRpcEntity.cs ├── PushEntities │ ├── DelegationModel.cs │ ├── HeadModel.cs │ ├── OriginationModel.cs │ ├── PushMessage.cs │ ├── SubscribeModel.cs │ └── TransactionModel.cs ├── RpcEntities │ ├── BakingRightsRpcEntity.cs │ ├── BlockRpcEntity.cs │ ├── ConstantsRpcEntity.cs │ ├── ContractRpcEntity.cs │ ├── DelegateRpcModel.cs │ ├── EndorsingRightsRpcEntity.cs │ ├── ErrorRpcEntity.cs │ ├── MonitorHeadModel.cs │ └── SelectedRollSnapshotRpcEntity.cs └── Typescript │ └── contracts.d.ts ├── AgileVentures.TezPusher.SampleClient.Web ├── .editorconfig ├── .gitignore ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── pipes.ts │ │ └── signalr.service.ts │ ├── assets │ │ └── .gitkeep │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.less │ ├── tsconfig.app.json │ └── tslint.json ├── tsconfig.json └── tslint.json ├── AgileVentures.TezPusher.SampleClient ├── .editorconfig ├── .gitignore ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── pipes.ts │ │ ├── serviceurl-dialog │ │ │ ├── serviceurl-dialog.component.html │ │ │ └── serviceurl-dialog.component.ts │ │ ├── signalr-connection-info.model.ts │ │ └── signalr.service.ts │ ├── assets │ │ └── .gitkeep │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.less │ ├── tsconfig.app.json │ └── tslint.json ├── tsconfig.json └── tslint.json ├── AgileVentures.TezPusher.Service ├── AgileVentures.TezPusher.Service.csproj └── AzureStorageHelper.cs ├── AgileVentures.TezPusher.Web ├── AgileVentures.TezPusher.Web.csproj ├── Configurations │ └── TezosConfig.cs ├── Dockerfile ├── HttpClients │ └── TezosMonitorClient.cs ├── Hubs │ └── TezosHub.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Services │ ├── PushHistoryService.cs │ ├── PushService.cs │ ├── TezosHistoryService.cs │ └── TezosMonitorService.cs ├── Startup.cs ├── appsettings.Development.json ├── appsettings.Production.json ├── appsettings.json └── docker-compose.yml ├── AgileVentures.TezPusher.sln ├── LICENSE ├── README.md ├── SUMMARY.md ├── docs-api-endpoints ├── docs-api-subscribe.md ├── docs-api-unsubscribe.md └── docs-negotiate.md ├── docs-clients ├── docs-docker.md ├── docs-taas-endpoint-or-function.md └── docs-typescript-definition.md ├── docs-getting-started ├── docs-using-docker-and-serveless.md ├── docs-using-docker.md └── docs-using-tezoslive.io-endpoint.md ├── docs-sample-clients ├── docs-agileventures.tezpusher.sampleclient.md └── docs-agileventures.tezpusher.sampleclient.web.md └── docs-welcome.md /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.vs 6 | **/.vscode 7 | **/*.*proj.user 8 | **/azds.yaml 9 | **/charts 10 | **/bin 11 | **/obj 12 | **/Dockerfile 13 | **/Dockerfile.develop 14 | **/docker-compose.yml 15 | **/docker-compose.*.yml 16 | **/*.dbmdl 17 | **/*.jfm 18 | **/secrets.dev.yaml 19 | **/values.dev.yaml 20 | **/.toolstarget -------------------------------------------------------------------------------- /.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/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-vscode.csharp" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": "AgileVentures.TezPusher.Function/bin/Release/netstandard2.0/publish", 3 | "azureFunctions.projectLanguage": "C#", 4 | "azureFunctions.projectRuntime": "~2", 5 | "debug.internalConsoleOptions": "neverOpen", 6 | "azureFunctions.preDeployTask": "publish" 7 | } -------------------------------------------------------------------------------- /.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.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 | -------------------------------------------------------------------------------- /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.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.ConsoleApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AgileVentures.TezPusher.ConsoleApp": { 4 | "commandName": "Project" 5 | }, 6 | "Docker": { 7 | "commandName": "Docker" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.ConsoleApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Azure": { 3 | "AzureFunctionUrl": "", 4 | "AzureFunctionKey": "" 5 | }, 6 | "Tezos": { 7 | "NodeUrl": "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.ConsoleApp/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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.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.Function/host.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Function/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "AzureWebJobsDashboard": "UseDevelopmentStorage=true", 6 | "AzureSignalRConnectionString": "Endpoint=https://taas.service.signalr.net;AccessKey=C1LpyHMwykH57AfnYyH28DhsWn7O2FAb+W7kLLtlMk8=;Version=1.0;" 7 | }, 8 | "Host": { 9 | "LocalHttpPort": 7071, 10 | "CORS": "*" 11 | } 12 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/AgileVentures.TezPusher.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/Configuration/ConsoleAppConfig.cs: -------------------------------------------------------------------------------- 1 | namespace AgileVentures.TezPusher.Model.Configuration 2 | { 3 | public class ConsoleAppConfig 4 | { 5 | public AzureConfig Azure { get; set; } 6 | public TezosConfig Tezos { get; set; } 7 | } 8 | 9 | public class AzureConfig 10 | { 11 | public string AzureFunctionUrl { get; set; } 12 | public string AzureFunctionKey { get; set; } 13 | } 14 | 15 | public class TezosConfig 16 | { 17 | public string NodeUrl { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/Constants/PushMessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace AgileVentures.TezPusher.Model.Constants 2 | { 3 | public class PushMessageTypes 4 | { 5 | public const string TransactionMessageType = "transaction"; 6 | public const string DelegationMessageType = "delegation"; 7 | public const string OriginationMessageType = "origination"; 8 | public const string BlockHeaderMessageType = "block_header"; 9 | } 10 | } -------------------------------------------------------------------------------- /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.Model/Contracts/BlockOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AgileVentures.TezPusher.Model.RpcEntities; 3 | 4 | namespace AgileVentures.TezPusher.Model.Contracts 5 | { 6 | public class BlockOperations 7 | { 8 | public List> Transactions { get; set; } 9 | public List> Originations { get; set; } 10 | public List> Delegations { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/Interfaces/IBlockLevelRpcEntity.cs: -------------------------------------------------------------------------------- 1 | namespace AgileVentures.TezPusher.Model.Interfaces 2 | { 3 | public interface IBlockLevelRpcEntity 4 | { 5 | long cycle { get; set; } 6 | int cycle_position { get; set; } 7 | bool expected_commitment { get; set; } 8 | long level { get; set; } 9 | long level_position { get; set; } 10 | int voting_period { get; set; } 11 | int voting_period_position { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/Interfaces/IRpcEntity.cs: -------------------------------------------------------------------------------- 1 | namespace AgileVentures.TezPusher.Model.Interfaces 2 | { 3 | public interface IRpcEntity 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /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/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.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/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.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/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.Model/RpcEntities/BakingRightsRpcEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AgileVentures.TezPusher.Model.Interfaces; 3 | 4 | namespace AgileVentures.TezPusher.Model.RpcEntities 5 | { 6 | public class BakingRightsRpcEntity : IRpcEntity 7 | { 8 | public List Rights { get; set; } 9 | } 10 | 11 | public class BakingRight 12 | { 13 | public long level { get; set; } 14 | public string @delegate { get; set; } 15 | public int priority { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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/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.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.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 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/RpcEntities/ErrorRpcEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AgileVentures.TezPusher.Model.Interfaces; 3 | 4 | namespace AgileVentures.TezPusher.Model.RpcEntities 5 | { 6 | public class ErrorRpcEntity : IRpcEntity 7 | { 8 | public string kind { get; set; } 9 | public string id { get; set; } 10 | public List missing_key { get; set; } 11 | public string function { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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.Model/RpcEntities/SelectedRollSnapshotRpcEntity.cs: -------------------------------------------------------------------------------- 1 | using AgileVentures.TezPusher.Model.Interfaces; 2 | 3 | namespace AgileVentures.TezPusher.Model.RpcEntities 4 | { 5 | public class SelectedRollSnapshotRpcEntity : IRpcEntity 6 | { 7 | public int selected_snapshot { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Model/Typescript/contracts.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tezos-live/push' { 2 | import { BlockDelegationContent, BlockOriginationContent, BlockTransactionContent, Header } from '@tezos-live'; 3 | 4 | export interface DelegationModel { 5 | blockHash: string; 6 | blockLevel: number; 7 | delegationContent: BlockDelegationContent; 8 | operationHash: string; 9 | timestamp: string; 10 | } 11 | 12 | export interface HeadModel extends Header { 13 | chain_id: string; 14 | hash: string; 15 | protocol: string; 16 | } 17 | 18 | export interface OriginationModel { 19 | blockHash: string; 20 | blockLevel: number; 21 | operationHash: string; 22 | originationContent: BlockOriginationContent; 23 | timestamp: string; 24 | } 25 | 26 | export interface PushMessage { 27 | blockHeader: HeadModel; 28 | delegation: DelegationModel; 29 | messageType: string; 30 | origination: OriginationModel; 31 | transaction: TransactionModel; 32 | } 33 | 34 | export interface SubscribeModel { 35 | delegationAddresses: string[]; 36 | originationAddresses: string[]; 37 | transactionAddresses: string[]; 38 | userId: string; 39 | } 40 | 41 | export interface TransactionModel { 42 | blockHash: string; 43 | blockLevel: number; 44 | operationHash: string; 45 | timestamp: string; 46 | transactionContent: BlockTransactionContent; 47 | } 48 | 49 | } 50 | 51 | declare module '@tezos-live' { 52 | 53 | export interface BakingRight { 54 | delegate: string; 55 | level: number; 56 | priority: number; 57 | } 58 | 59 | export interface BakingRightsRpcEntity { 60 | rights: BakingRight[]; 61 | } 62 | 63 | export interface BalanceUpdate { 64 | category: string; 65 | change: string; 66 | contract: string; 67 | cycle?: number; 68 | delegate: string; 69 | kind: string; 70 | } 71 | 72 | export interface BlockDelegationContent extends BlockOperationContent { 73 | delegate: string; 74 | metadata: BlockTransactionMetadata; 75 | } 76 | 77 | export interface BlockDelegationsRpcEntity extends BlockOperation[] { 78 | } 79 | 80 | export interface BlockDoubleBakingBlockHash { 81 | context: string; 82 | fitness: string[]; 83 | level: number; 84 | operations_hash: string; 85 | predecessor: string; 86 | priority: number; 87 | proof_of_work_nonce: string; 88 | proto: number; 89 | signature: string; 90 | timestamp: string; 91 | validation_pass: number; 92 | } 93 | 94 | export interface BlockDoubleBakingContent { 95 | bh1: BlockDoubleBakingBlockHash; 96 | bh2: BlockDoubleBakingBlockHash; 97 | kind: string; 98 | metadata: Metadata; 99 | } 100 | 101 | export interface BlockDoubleBakingsRpcEntity extends BlockOperation[] { 102 | } 103 | 104 | export interface BlockEndorsementBalanceUpdate { 105 | category: string; 106 | change: string; 107 | contract: string; 108 | delegate: string; 109 | kind: string; 110 | level?: number; 111 | } 112 | 113 | export interface BlockEndorsementContent { 114 | kind: string; 115 | level: number; 116 | metadata: BlockTransactionMetadata; 117 | } 118 | 119 | export interface BlockEndorsementMetadata { 120 | balance_updates: BlockEndorsementBalanceUpdate[]; 121 | delegate: string; 122 | slots: number[]; 123 | } 124 | 125 | export interface BlockEndorsementsRpcEntity extends BlockOperation[] { 126 | } 127 | 128 | export interface BlockLevelRpcEntity { 129 | cycle: number; 130 | cycle_position: number; 131 | expected_commitment: boolean; 132 | level: number; 133 | level_position: number; 134 | voting_period: number; 135 | voting_period_position: number; 136 | } 137 | 138 | export interface BlockOperation { 139 | branch: string; 140 | chain_id: string; 141 | contents: T[]; 142 | hash: string; 143 | protocol: string; 144 | signature: string; 145 | } 146 | 147 | export interface BlockOperationContent { 148 | counter: string; 149 | fee: string; 150 | gas_limit: string; 151 | kind: string; 152 | source: string; 153 | storage_limit: string; 154 | } 155 | 156 | export interface BlockOperationsRpcEntity extends any[][] { 157 | } 158 | 159 | export interface BlockOriginationContent extends BlockOperationContent { 160 | balance: string; 161 | metadata: BlockTransactionMetadata; 162 | } 163 | 164 | export interface BlockOriginationsRpcEntity extends BlockOperation[] { 165 | } 166 | 167 | export interface BlockRpcEntity { 168 | chain_id: string; 169 | hash: string; 170 | header: Header; 171 | metadata: Metadata; 172 | operations: BlockOperationsRpcEntity; 173 | protocol: string; 174 | } 175 | 176 | export interface BlockTransactionBalanceUpdate { 177 | category: string; 178 | change: string; 179 | contract: string; 180 | cycle?: number; 181 | delegate: string; 182 | kind: string; 183 | } 184 | 185 | export interface BlockTransactionContent extends BlockOperationContent { 186 | amount: string; 187 | destination: string; 188 | metadata: BlockTransactionMetadata; 189 | } 190 | 191 | export interface BlockTransactionInternalOperationResult { 192 | amount: string; 193 | destination: string; 194 | kind: string; 195 | nonce: number; 196 | result: BlockTransactionOperationResult; 197 | source: string; 198 | } 199 | 200 | export interface BlockTransactionMetadata { 201 | balance_updates: BlockTransactionBalanceUpdate[]; 202 | internal_operation_results: BlockTransactionInternalOperationResult[]; 203 | operation_result: BlockTransactionOperationResult; 204 | } 205 | 206 | export interface BlockTransactionOperationResult { 207 | balance_updates: BlockTransactionBalanceUpdate[]; 208 | consumed_gas: string; 209 | originated_contracts: string[]; 210 | paid_storage_size_diff: string; 211 | status: string; 212 | storage_size: string; 213 | } 214 | 215 | export interface BlockTransactionsRpcEntity extends BlockOperation[] { 216 | } 217 | 218 | export interface ConstantsRpcEntity { 219 | block_reward: string; 220 | block_security_deposit: string; 221 | blocks_per_commitment: number; 222 | blocks_per_cycle: number; 223 | blocks_per_roll_snapshot: number; 224 | blocks_per_voting_period: number; 225 | cost_per_byte: string; 226 | dictator_pubkey: string; 227 | endorsement_reward: string; 228 | endorsement_security_deposit: string; 229 | endorsers_per_block: number; 230 | hard_gas_limit_per_block: string; 231 | hard_gas_limit_per_operation: string; 232 | hard_storage_limit_per_block: string; 233 | hard_storage_limit_per_operation: string; 234 | max_operation_data_length: number; 235 | max_revelations_per_block: number; 236 | michelson_maximum_type_size: number; 237 | nonce_length: number; 238 | origination_burn: string; 239 | preserved_cycles: number; 240 | proof_of_work_nonce_size: number; 241 | proof_of_work_threshold: string; 242 | seed_nonce_revelation_tip: string; 243 | time_between_blocks: string[]; 244 | tokens_per_roll: string; 245 | } 246 | 247 | export interface ContractBalanceRpcEntity { 248 | balance: string; 249 | } 250 | 251 | export interface ContractRpcEntity { 252 | balance: string; 253 | counter: number; 254 | delegate: Delegate; 255 | manager: string; 256 | spendable: boolean; 257 | } 258 | 259 | export interface Delegate { 260 | setable: boolean; 261 | } 262 | 263 | export interface DelegateBalanceRpcEntity { 264 | balance: string; 265 | } 266 | 267 | export interface DelegateRpcEntity { 268 | balance: string; 269 | deactivated: boolean; 270 | delegated_balance: string; 271 | delegated_contracts: string[]; 272 | frozen_balance: string; 273 | frozen_balance_by_cycle: FrozenBalanceByCycle[]; 274 | grace_period: number; 275 | staking_balance: string; 276 | } 277 | 278 | export interface DelegateRpcEntityError { 279 | errors: DelegateRpcEntityErrorDetails[]; 280 | } 281 | 282 | export interface DelegateRpcEntityErrorDetails { 283 | _function: string; 284 | id: string; 285 | kind: string; 286 | missing_key: string[]; 287 | } 288 | 289 | export interface DelegatedContractsRpcEntity { 290 | delegated_contracts: string[]; 291 | } 292 | 293 | export interface EndorsingRight { 294 | delegate: string; 295 | level: number; 296 | slots: number[]; 297 | } 298 | 299 | export interface EndorsingRightsRpcEntity { 300 | rights: EndorsingRight[]; 301 | } 302 | 303 | export interface ErrorRpcEntity { 304 | _function: string; 305 | id: string; 306 | kind: string; 307 | missing_key: string[]; 308 | } 309 | 310 | export interface FrozenBalanceByCycle { 311 | cycle: number; 312 | deposit: string; 313 | fees: string; 314 | rewards: string; 315 | } 316 | 317 | export interface GenericBlockOperationContent extends BlockOperationContent { 318 | amount: string; 319 | balance: string; 320 | delegate: string; 321 | destination: string; 322 | metadata: BlockTransactionMetadata; 323 | } 324 | 325 | export interface GenericOperationsEntity extends BlockOperation[] { 326 | } 327 | 328 | export interface Header { 329 | context: string; 330 | fitness: string[]; 331 | level: number; 332 | operations_hash: string; 333 | predecessor: string; 334 | priority: number; 335 | proof_of_work_nonce: string; 336 | proto: number; 337 | signature: string; 338 | timestamp: string; 339 | validation_pass: number; 340 | } 341 | 342 | export interface MaxOperationListLength { 343 | max_op: number; 344 | max_size: number; 345 | } 346 | 347 | export interface Metadata { 348 | baker: string; 349 | balance_updates: BalanceUpdate[]; 350 | consumed_gas: string; 351 | deactivated: any[]; 352 | level: BlockLevelRpcEntity; 353 | max_block_header_length: number; 354 | max_operation_data_length: number; 355 | max_operation_list_length: MaxOperationListLength[]; 356 | max_operations_ttl: number; 357 | next_protocol: string; 358 | nonce_hash: any; 359 | protocol: string; 360 | test_chain_status: TestChainStatus; 361 | voting_period_kind: string; 362 | } 363 | 364 | export interface MonitorHeadModel { 365 | context: string; 366 | fitness: string[]; 367 | hash: string; 368 | level: number; 369 | operations_hash: string; 370 | predecessor: string; 371 | proto: number; 372 | protocol_data: string; 373 | timestamp: string; 374 | validation_pass: number; 375 | } 376 | 377 | export interface SelectedRollSnapshotRpcEntity { 378 | selected_snapshot: number; 379 | } 380 | 381 | export interface TestChainStatus { 382 | status: string; 383 | } 384 | 385 | } 386 | 387 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /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.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.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.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 |

12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
Level {{element.level}} Hash {{element.hash}} Timestamp {{element.timestamp | date:'medium'}} Validation Pass {{element.validation_pass}}
50 | 51 |
52 | 53 |

Waiting for the new block on Tezos mainnet ...

54 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
Source {{element.transaction_content.source}} Destination {{element.transaction_content.destination}} Amount {{element.transaction_content.amount | amountToTez}} Timestamp {{element.timestamp | date:'medium'}} Block Level {{element.block_level}}
98 | 99 |
100 | 101 |

Waiting for the new transactions on Tezos mainnet ...

102 | 103 |
104 |
105 |
106 | 107 | 108 | 109 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 |
Source {{element.origination_content.source}} Originated {{element.origination_content.metadata.operation_result.originated_contracts[0]}} Fee {{element.origination_content.fee | feeToTez}} Timestamp {{element.timestamp | date:'medium'}} Block Level {{element.block_level}}
146 | 147 |
148 | 149 |

Waiting for the new originations on Tezos mainnet ...

150 | 151 |
152 |
153 |
154 | 155 | 156 | 157 | 158 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 |
Source {{element.delegation_content.source}} Delegate {{element.delegation_content.delegate}} Fee {{element.delegation_content.fee | feeToTez}} Timestamp {{element.timestamp | date:'medium'}} Block Level {{element.block_level}}
194 | 195 | 196 |
197 | 198 |

Waiting for the new delegations on Tezos mainnet ...

199 | 200 |
201 |
202 |
203 |
-------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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.SampleClient.Web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agile-ventures/TaaS/8f6a41ea1d3a5dac9a8a2e0a7ba34caf65bfdaf0/AgileVentures.TezPusher.SampleClient.Web/src/assets/.gitkeep -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /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.Web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agile-ventures/TaaS/8f6a41ea1d3a5dac9a8a2e0a7ba34caf65bfdaf0/AgileVentures.TezPusher.SampleClient.Web/src/favicon.ico -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TAAS (Tezos as a Service) Client Side Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /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.Web/src/styles.less: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 2 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient.Web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /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/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.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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/app/serviceurl-dialog/serviceurl-dialog.component.html: -------------------------------------------------------------------------------- 1 |

Please confirm your Service Endpoint

2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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.SampleClient/src/app/signalr-connection-info.model.ts: -------------------------------------------------------------------------------- 1 | export class SignalRConnectionInfo { 2 | url: string; 3 | accessToken: string; 4 | } -------------------------------------------------------------------------------- /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.SampleClient/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agile-ventures/TaaS/8f6a41ea1d3a5dac9a8a2e0a7ba34caf65bfdaf0/AgileVentures.TezPusher.SampleClient/src/assets/.gitkeep -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /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.SampleClient/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agile-ventures/TaaS/8f6a41ea1d3a5dac9a8a2e0a7ba34caf65bfdaf0/AgileVentures.TezPusher.SampleClient/src/favicon.ico -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TAAS (Tezos as a Service) Client Side Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /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/src/styles.less: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 2 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.SampleClient/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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.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.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/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.Web/Configurations/TezosConfig.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AgileVentures.TezPusher.Web.Configurations 4 | { 5 | public class TezosConfig 6 | { 7 | [Required(ErrorMessage = "Please input a valid URL in ENV settings under Tezos:NodeUrl key.")] 8 | [Url(ErrorMessage = "Please input a valid URL in ENV settings under Tezos:NodeUrl key.")] 9 | public string NodeUrl { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace AgileVentures.TezPusher.Web 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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.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 | } -------------------------------------------------------------------------------- /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.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.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.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Tezos": { 10 | "NodeUrl": "" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Web/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "System": "Error", 6 | "Microsoft": "Error" 7 | }, 8 | "Console": { 9 | "IncludeScopes": false 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /AgileVentures.TezPusher.Web/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: . 5 | network_mode: bridge 6 | image: tezoslive/agileventurestezpusherweb 7 | ports: 8 | - "80:80" 9 | environment: 10 | - Tezos:NodeUrl: "" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | --------------------------------------------------------------------------------