Repository: project-origin/wallet
Pull request #578: add enable endpoint+dotnet format
Author: login: sahma19
Created: 2025-05-07T17:51:07Z | Merged: 2025-05-09T11:21:37Z



Diff:
diff --git a/src/ProjectOrigin.Vault/Repositories/IWalletRepository.cs b/src/ProjectOrigin.Vault/Repositories/IWalletRepository.cs
index 4ee7e691..26dc334c 100644
--- a/src/ProjectOrigin.Vault/Repositories/IWalletRepository.cs
+++ b/src/ProjectOrigin.Vault/Repositories/IWalletRepository.cs
@@ -22,5 +22,6 @@ public interface IWalletRepository
     Task<ExternalEndpoint> CreateExternalEndpoint(string owner, IHDPublicKey ownerPublicKey, string referenceText, string endpoint);
     Task<ExternalEndpoint> GetExternalEndpoint(Guid endpointId);
 
+    Task EnableWallet(Guid walletId);
     Task DisableWallet(Guid walletId, DateTimeOffset disabledDateUtc);
 }
diff --git a/src/ProjectOrigin.Vault/Repositories/WalletRepository.cs b/src/ProjectOrigin.Vault/Repositories/WalletRepository.cs
index 1af89ac0..71b90cfa 100644
--- a/src/ProjectOrigin.Vault/Repositories/WalletRepository.cs
+++ b/src/ProjectOrigin.Vault/Repositories/WalletRepository.cs
@@ -48,6 +48,21 @@ FROM wallets
             });
     }
 
+    public async Task EnableWallet(Guid walletId)
+    {
+        var rowsChanged = await _connection.ExecuteAsync(
+            @"UPDATE wallets
+          SET disabled = NULL
+          WHERE id = @walletId",
+            new
+            {
+                walletId
+            });
+
+        if (rowsChanged != 1)
+            throw new InvalidOperationException($"Wallet with id {walletId} could not be found");
+    }
+
     public async Task DisableWallet(Guid walletId, DateTimeOffset disabledDateUtc)
     {
         var rowsChanged = await _connection.ExecuteAsync(
diff --git a/src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs b/src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs
index 8aca3722..1cc60ad1 100644
--- a/src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs
+++ b/src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs
@@ -199,6 +199,43 @@ public async Task<ActionResult<DisableWalletResponse>> DisableWallet(
         });
     }
 
+    /// <summary>
+    /// Enables a previously disabled wallet, allowing it to interact with transfers, claims, querying, etc.
+    /// </summary>
+    /// <param name="unitOfWork"></param>
+    /// <param name="walletId">The ID of the wallet to enable.</param>
+    /// <response code="200">The wallet was found and is now enabled.</response>
+    /// <response code="401">If the user is not authenticated.</response>
+    /// <response code="404">If the wallet specified is not found.</response>
+    [HttpPut]
+    [Route("v1/wallets/{walletId}/enable")]
+    [RequiredScope("po:wallets:update")]
+    [Produces("application/json")]
+    [ProducesResponseType(StatusCodes.Status200OK)]
+    [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
+    [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
+    public async Task<ActionResult<EnableWalletResponse>> EnableWallet(
+        [FromServices] IUnitOfWork unitOfWork,
+        [FromRoute] Guid walletId)
+    {
+        if (!User.TryGetSubject(out var subject)) return Unauthorized();
+
+        var wallet = await unitOfWork.WalletRepository.GetWallet(walletId);
+
+        if (wallet is null || wallet.Owner != subject) return NotFound();
+
+        if (wallet.IsDisabled())
+        {
+            await unitOfWork.WalletRepository.EnableWallet(walletId);
+            unitOfWork.Commit();
+        }
+
+        return Ok(new EnableWalletResponse
+        {
+            WalletId = wallet.Id,
+        });
+    }
+
     /// <summary>
     /// Creates a new wallet endpoint on the users wallet, which can be sent to other services to receive certificate-slices.
     /// </summary>
@@ -349,6 +386,17 @@ public record DisableWalletResponse()
     public required long DisabledDate { get; init; }
 }
 
+/// <summary>
+/// Response to enable a wallet.
+/// </summary>
+public record EnableWalletResponse()
+{
+    /// <summary>
+    /// The ID of the wallet.
+    /// </summary>
+    public Guid WalletId { get; init; }
+}
+
 /// <summary>
 /// Response to create a new wallet endpoint on the users wallet.
 /// </summary>
diff --git a/test/ProjectOrigin.Vault.Tests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt b/test/ProjectOrigin.Vault.Tests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
index 671068a2..9b89db53 100644
--- a/test/ProjectOrigin.Vault.Tests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
+++ b/test/ProjectOrigin.Vault.Tests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
@@ -1107,6 +1107,44 @@
         }
       }
     },
+    "/v1/wallets/{walletId}/enable": {
+      "put": {
+        "tags": [
+          "Wallet"
+        ],
+        "summary": "Enables a previously disabled wallet, allowing it to interact with transfers, claims, querying, etc.",
+        "parameters": [
+          {
+            "name": "walletId",
+            "in": "path",
+            "description": "The ID of the wallet to enable.",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "format": "uuid"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "The wallet was found and is now enabled.",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/EnableWalletResponse"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "If the user is not authenticated."
+          },
+          "404": {
+            "description": "If the wallet specified is not found."
+          }
+        }
+      }
+    },
     "/v1/wallets/{walletId}/endpoints": {
       "post": {
         "tags": [
@@ -1605,6 +1643,18 @@
         "additionalProperties": false,
         "description": "Response to disable a wallet."
       },
+      "EnableWalletResponse": {
+        "type": "object",
+        "properties": {
+          "walletId": {
+            "type": "string",
+            "description": "The ID of the wallet.",
+            "format": "uuid"
+          }
+        },
+        "additionalProperties": false,
+        "description": "Response to enable a wallet."
+      },
       "FederatedStreamId": {
         "required": [
           "registry",
diff --git a/test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs b/test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
index 0919b23d..c1541668 100644
--- a/test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
+++ b/test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
@@ -112,6 +112,88 @@ public async Task DisableWallet()
         Assert.NotNull(walletDb.Disabled);
     }
 
+    // Tests for EnableWallet endpoint
+    [Fact]
+    public async Task EnableWallet_Verify_Unauthorized()
+    {
+        // Arrange
+        var controller = new WalletController();
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<UnauthorizedResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenNoWallet_NotFound()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<NotFoundResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenWalletIsAlreadyEnabled()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public async Task EnableWallet()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+        await _dbFixture.DisableWallet(wallet.Id);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var response = result.Result.As<OkObjectResult>().Value.As<EnableWalletResponse>();
+        response.Should().NotBeNull();
+        response.WalletId.Should().Be(wallet.Id);
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
     [Fact]
     public async Task Verify_Unauthorized()
     {


Reviews:
user: login: tnickelsen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T11:03:16Z
user: login: sahma19
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T11:04:16Z
user: login: sahma19
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T11:16:00Z
user: login: sahma19
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T11:16:08Z
user: login: tnickelsen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T11:21:27Z
user: login: tnickelsen
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T11:21:36Z

Review comments:
diff_hunk: @@ -349,6 +387,21 @@ public record DisableWalletResponse()
     public required long DisabledDate { get; init; }
 }
 
+/// <summary>
+/// Response to enable a wallet.
+/// </summary>
+public record EnableWalletResponse()
path: src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs
user: login: tnickelsen
body: Bare returner walletId. Message er implicit i OK reponse
created_at: 2025-05-09T11:01:40Z
author_association: MEMBER
diff_hunk: @@ -112,6 +112,103 @@ public async Task DisableWallet()
         Assert.NotNull(walletDb.Disabled);
     }
 
+    // Tests for EnableWallet endpoint
+    [Fact]
+    public async Task EnableWallet_Verify_Unauthorized()
+    {
+        // Arrange
+        var controller = new WalletController();
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<UnauthorizedResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenNoWallet_NotFound()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<NotFoundResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenWalletIsAlreadyEnabled()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public async Task EnableWallet()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+        await _dbFixture.DisableWallet(wallet.Id);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public void EnableWalletResponse_ShouldHaveCorrectProperties()
path: test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
user: login: tnickelsen
body: Slet denne test og lav et tjek for, at de har samme walletId i EnableWallet() testen
created_at: 2025-05-09T11:02:47Z
author_association: MEMBER
diff_hunk: @@ -112,6 +112,103 @@ public async Task DisableWallet()
         Assert.NotNull(walletDb.Disabled);
     }
 
+    // Tests for EnableWallet endpoint
+    [Fact]
+    public async Task EnableWallet_Verify_Unauthorized()
+    {
+        // Arrange
+        var controller = new WalletController();
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<UnauthorizedResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenNoWallet_NotFound()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<NotFoundResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenWalletIsAlreadyEnabled()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public async Task EnableWallet()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+        await _dbFixture.DisableWallet(wallet.Id);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public void EnableWalletResponse_ShouldHaveCorrectProperties()
path: test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
user: login: sahma19
body: Den er der for at tilfredsstille SonarCloud også. så vil helst have den bliver
created_at: 2025-05-09T11:04:16Z
author_association: COLLABORATOR
diff_hunk: @@ -112,6 +112,103 @@ public async Task DisableWallet()
         Assert.NotNull(walletDb.Disabled);
     }
 
+    // Tests for EnableWallet endpoint
+    [Fact]
+    public async Task EnableWallet_Verify_Unauthorized()
+    {
+        // Arrange
+        var controller = new WalletController();
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<UnauthorizedResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenNoWallet_NotFound()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<NotFoundResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenWalletIsAlreadyEnabled()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public async Task EnableWallet()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+        await _dbFixture.DisableWallet(wallet.Id);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public void EnableWalletResponse_ShouldHaveCorrectProperties()
path: test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
user: login: sahma19
body: Rettede det alligevel: https://github.com/project-origin/wallet/pull/578/commits/8e66283bdad251f74dea06658695aa8885128daf
created_at: 2025-05-09T11:16:00Z
author_association: COLLABORATOR
diff_hunk: @@ -349,6 +387,21 @@ public record DisableWalletResponse()
     public required long DisabledDate { get; init; }
 }
 
+/// <summary>
+/// Response to enable a wallet.
+/// </summary>
+public record EnableWalletResponse()
path: src/ProjectOrigin.Vault/Services/REST/v1/WalletController.cs
user: login: sahma19
body: Fixed: https://github.com/project-origin/wallet/pull/578/commits/8e66283bdad251f74dea06658695aa8885128daf
created_at: 2025-05-09T11:16:07Z
author_association: COLLABORATOR
diff_hunk: @@ -112,6 +112,103 @@ public async Task DisableWallet()
         Assert.NotNull(walletDb.Disabled);
     }
 
+    // Tests for EnableWallet endpoint
+    [Fact]
+    public async Task EnableWallet_Verify_Unauthorized()
+    {
+        // Arrange
+        var controller = new WalletController();
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<UnauthorizedResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenNoWallet_NotFound()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, Guid.NewGuid());
+
+        // Assert
+        result.Result.Should().BeOfType<NotFoundResult>();
+    }
+
+    [Fact]
+    public async Task EnableWallet_WhenWalletIsAlreadyEnabled()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public async Task EnableWallet()
+    {
+        // Arrange
+        var subject = _fixture.Create<string>();
+        var controller = new WalletController()
+        {
+            ControllerContext = CreateContextWithUser(subject)
+        };
+
+        var wallet = await _dbFixture.CreateWallet(subject);
+        await _dbFixture.DisableWallet(wallet.Id);
+
+        // Act
+        var result = await controller.EnableWallet(_unitOfWork, wallet.Id);
+
+        // Assert
+        result.Result.Should().BeOfType<OkObjectResult>();
+
+        var walletDb = await _unitOfWork.WalletRepository.GetWallet(wallet.Id);
+        Assert.NotNull(walletDb);
+        Assert.Null(walletDb.Disabled);
+    }
+
+    [Fact]
+    public void EnableWalletResponse_ShouldHaveCorrectProperties()
path: test/ProjectOrigin.Vault.Tests/REST/WalletControllerTests.cs
user: login: tnickelsen
body: Hmm synes ikke jeg kan se at sonar brokker sig over den del? Men jeg har approved
created_at: 2025-05-09T11:21:27Z
author_association: MEMBER

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-09T11:18:59Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=project-origin_wallet&pullRequest=578) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=project-origin_wallet&pullRequest=578&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=project-origin_wallet&pullRequest=578&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=project-origin_wallet&pullRequest=578&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [94.4% Coverage on New Code](https://sonarcloud.io/component_measures?id=project-origin_wallet&pullRequest=578&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=project-origin_wallet&pullRequest=578&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=project-origin_wallet&pullRequest=578)


author_association: NONE