Skip to content

Commit

Permalink
Merge pull request #2 from yusuf-cirak/feat/attestation-vehicle-crede…
Browse files Browse the repository at this point in the history
…ntials

Implement Vehicle VIN Credential Retrieval via Attestation API
  • Loading branch information
Asfiroth authored Jan 7, 2025
2 parents 8d9c797 + 29dfc9f commit 1c48e30
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 17 deletions.
18 changes: 17 additions & 1 deletion example/Dimo.Example.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,20 @@ query ListVehiclesDefinitionsPerDeviceDefinitionId($deviceDefinitionId: String!,
Console.WriteLine($"Make: {node.Definition.Make}, Model: {node.Definition.Model}, Year: {node.Definition.Year}");
}
}
*/
*/


#region Create Verifiable Credentials
// // Create verifiable vin vc and get the latest vin vc
// var tokenId = 0;
//
// var vehicleToken = "";
//
// var vinVc = await dimoClient.AttestationService.CreateVinVcAsync(tokenId, vehicleToken);
//
// Console.WriteLine(vinVc);
//
// var vinVcLatest = await dimoClient.TelemetryService.GetVehicleVinVcLatestAsync(tokenId, vehicleToken);
//
// Console.WriteLine(vinVcLatest);
#endregion
11 changes: 11 additions & 0 deletions src/Dimo.Client/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum DimoEnvironment { Development, Production }

internal static class ProdApiUrls
{
public const string Attestation = "https://attestation-api.dimo.zone";
public const string Auth = "https://auth.dimo.zone";
public const string Devices = "https://devices-api.dimo.zone";
public const string DeviceData = "https://device-data-api.dimo.zone";
Expand All @@ -25,6 +26,7 @@ internal static class ProdApiUrls

internal static class DevApiUrls
{
public const string Attestation = "https://attestation-api.dev.dimo.zone";
public const string Auth = "https://auth.dev.dimo.zone";
public const string Devices = "https://devices-api.dev.dimo.zone";
public const string DeviceData = "https://device-data-api.dev.dimo.zone";
Expand All @@ -41,6 +43,7 @@ internal static class DevApiUrls

internal static class ApiNames
{
public const string Attestation = "AttestationApi";
public const string Auth = "AuthApi";
public const string Devices = "DevicesApi";
public const string DeviceData = "DeviceDataApi";
Expand Down Expand Up @@ -112,6 +115,10 @@ internal static class Constants
ApiNames.Telemetry,
DevApiUrls.Telemetry
},
{
ApiNames.Attestation,
DevApiUrls.Attestation
}
}

},
Expand Down Expand Up @@ -166,6 +173,10 @@ internal static class Constants
ApiNames.Telemetry,
ProdApiUrls.Telemetry
},
{
ApiNames.Attestation,
ProdApiUrls.Attestation
}
}
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/Dimo.Client/DimoClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Dimo.Client.Extensions;
using Dimo.Client.Services.Attestation;
using Dimo.Client.Services.Authentication;
using Dimo.Client.Services.DeviceData;
using Dimo.Client.Services.DeviceDefinitions;
Expand All @@ -19,6 +20,7 @@ namespace Dimo.Client
{
public interface IDimoClient : IAsyncDisposable, IDisposable
{
IAttestationService AttestationService { get; }
IAuthenticationService AuthenticationService { get; }
IDeviceDataService DeviceDataService { get; }
IDeviceDefinitionsService DeviceDefinitionsService { get; }
Expand All @@ -35,6 +37,7 @@ public interface IDimoClient : IAsyncDisposable, IDisposable

internal class DimoClient : IDimoClient
{
public IAttestationService AttestationService => _provider.GetRequiredService<IAttestationService>();
public IAuthenticationService AuthenticationService => _provider.GetRequiredService<IAuthenticationService>();
public IDeviceDataService DeviceDataService => _provider.GetRequiredService<IDeviceDataService>();
public IDeviceDefinitionsService DeviceDefinitionsService => _provider.GetRequiredService<IDeviceDefinitionsService>();
Expand Down
2 changes: 0 additions & 2 deletions src/Dimo.Client/Extensions/GraphqlServicesExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using Dimo.Client.Services.Authentication;
using Dimo.Client.Services.Identity;
using Dimo.Client.Services.Telemetry;
using Dimo.Client.Services.TokenExchange;
using Microsoft.Extensions.DependencyInjection;

namespace Dimo.Client.Extensions
Expand Down
2 changes: 2 additions & 0 deletions src/Dimo.Client/Extensions/RestServicesExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Dimo.Client.Services.Attestation;
using Dimo.Client.Services.Authentication;
using Dimo.Client.Services.DeviceData;
using Dimo.Client.Services.DeviceDefinitions;
Expand All @@ -17,6 +18,7 @@ public static class RestServicesExtensions
{
internal static IServiceCollection AddDimoRestServices(this IServiceCollection services)
{
services.AddScoped<IAttestationService, AttestationService>();
services.AddScoped<IDeviceDataService, DeviceDataService>();
services.AddScoped<IDeviceDefinitionsService, DeviceDefinitionService>();
services.AddScoped<IDevicesService, DevicesService>();
Expand Down
19 changes: 19 additions & 0 deletions src/Dimo.Client/Models/VehicleVinVc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Dimo.Client.Models
{

#if NETSTANDARD
public class VehicleVinVc
{
public string VcUrl { get; set; }
public string VcQuery { get; set; }
public string Message { get; set; }
}
#elif NET6_0_OR_GREATER
public record VehicleVinVc
{
public string VcUrl { get; init; }
public string VcQuery { get; init; }
public string Message { get; init; }
}
#endif
}
48 changes: 48 additions & 0 deletions src/Dimo.Client/Models/VehicleVinVcLatest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;

#if NETSTANDARD
using Newtonsoft.Json;
#elif NET6_0_OR_GREATER
using System.Text.Json.Serialization;
#endif

namespace Dimo.Client.Models
{
#if NETSTANDARD
public class VinVcLatestScheme<T>
{
[JsonProperty("vinVCLatest")]
public T VinVcLatest { get; set; }
}

public class VehicleVinVcLatest
{
public long VehicleTokenId { get; set; }
public string Vin { get; set; }
public string RecordedBy { get; set; }
public DateTime RecordedAt { get; set; }
public string CountryCode { get; set; }
public string VehicleContractAddress { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }

[JsonProperty("rawVC")]
public string RawVc { get; set; }
}
#elif NET6_0_OR_GREATER

public record VinVcLatestScheme<T>(T VinVcLatest);

public record VehicleVinVcLatest(
long VehicleTokenId,
string Vin,
string RecordedBy,
DateTime RecordedAt,
string CountryCode,
string VehicleContractAddress,
DateTime ValidFrom,
DateTime ValidTo,
string RawVc);

#endif
}
57 changes: 57 additions & 0 deletions src/Dimo.Client/Services/Attestation/AttestationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Dimo.Client.Models;

#if NETSTANDARD
using Newtonsoft.Json;
#elif NET6_0_OR_GREATER
using System.Net.Http.Json;
#endif

namespace Dimo.Client.Services.Attestation
{
public sealed class AttestationService : IAttestationService
{
private readonly IHttpClientFactory _httpClientFactory;

public AttestationService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<VehicleVinVc> CreateVinVcAsync(long tokenId, string vehicleToken,
CancellationToken cancellationToken = default)
{
if (tokenId <= 0)
{
throw new ArgumentException("TokenId must be greater than 0", nameof(tokenId));
}

if (string.IsNullOrWhiteSpace(vehicleToken))
throw new ArgumentException("Vehicle token must not be null or empty", nameof(vehicleToken));


const string path = "/v1/vc/vin/{0}";

using (var client = _httpClientFactory.CreateClient(ApiNames.Attestation))
{
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", vehicleToken);

string requestUri = string.Format(path, tokenId);

var response = await client.PostAsync(requestUri, null, cancellationToken).ConfigureAwait(false);

response.EnsureSuccessStatusCode();

#if NETSTANDARD
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<VehicleVinVc>(json);
#elif NET6_0_OR_GREATER
return await response.Content.ReadFromJsonAsync<VehicleVinVc>(cancellationToken: cancellationToken).ConfigureAwait(false);
#endif
}
}
}
}
35 changes: 35 additions & 0 deletions src/Dimo.Client/Services/Attestation/IAttestationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Dimo.Client.Models;

namespace Dimo.Client.Services.Attestation
{
public interface IAttestationService
{
/// <summary>
/// Creates or retrieves a Vehicle Identification Number (VIN) Verifiable Credential (VC) for a specific vehicle.
/// If a VC has never been created or has expired, generates a new one. Otherwise, returns the existing unexpired VC.
/// The resulting rawVC can be used for querying the Telemetry API to obtain vehicle VIN information.
/// </summary>
/// <param name="tokenId">The vehicle's NFT token ID. The token must have permission to access the vehicle VIN data.</param>
/// <param name="vehicleToken">The vehicle's JWT token for authentication.</param>
/// /// <param name="cancellationToken">Token for signalling the cancellation of the operation.</param>
/// <returns>
/// Returns a VinVcResponse containing:
/// - vcUrl: The GraphQL endpoint URL for querying the VC
/// - vcQuery: The GraphQL query template to retrieve the rawVC
/// - message: Success confirmation message with retrieval instructions
/// </returns>
/// <exception cref="ArgumentException">Thrown when tokenId or vehicleToken is null or empty.</exception>
/// <exception cref="HttpRequestException">Thrown when the HTTP response was unsuccessful.</exception>
/// <exception cref="Exception">
/// Thrown when:
/// - The request is invalid (400)
/// - Authentication fails
/// - The token lacks necessary permissions
/// </exception>
Task<VehicleVinVc> CreateVinVcAsync(long tokenId, string vehicleToken,CancellationToken cancellationToken = default);
}
}
32 changes: 32 additions & 0 deletions src/Dimo.Client/Services/Telemetry/ITelemetryService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Dimo.Client.Exceptions;
using Dimo.Client.Models;

namespace Dimo.Client.Services.Telemetry
{
public interface ITelemetryService
{
/// <summary>
/// Retrieves the latest Vehicle Identification Number (VIN) Verifiable Credential (VC) for a specific vehicle.
/// This method should be called after successfully calling CreateVinVcAsync to get the actual VC data.
/// </summary>
/// <param name="tokenId">The vehicle's NFT token ID as a long integer</param>
/// <param name="vehicleToken">The vehicle's JWT token for authentication</param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
/// <exception cref="ArgumentException">
/// Thrown when:
/// - tokenId is invalid
/// - vehicleToken is null or empty
/// </exception>
/// <exception cref="DimoGraphqlException">
/// Thrown when:
/// - The API request fails
/// - Authentication fails
/// - The token lacks necessary permissions
/// - No VC exists for the given token ID
/// </exception>
/// <remarks>
/// Typical workflow:
/// 1. First call CreateVinVcAsync to ensure a VC exists
/// 2. Then call this method to retrieve the actual VC data
///
/// Example usage:
/// var vc = await client.CreateVinVcAsync(tokenId, vehicleToken);
/// var latestVc = await client.GetVinVcLatestAsync(tokenId, vehicleToken);
/// </remarks>

Task<VinVcLatestScheme<VehicleVinVcLatest>> GetVehicleVinVcLatestAsync(long tokenId,string vehicleToken, CancellationToken cancellationToken = default);
Task<LatestSignal> GetLatestSignalsAsync(long tokenId, string authToken, CancellationToken cancellationToken = default);
Task<T> ExecuteQueryAsync<T>(string authToken, [StringSyntax("GraphQL")]string query, object variables, string queryName = "", CancellationToken cancellationToken = default);
}
Expand Down
Loading

0 comments on commit 1c48e30

Please sign in to comment.