Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 3, 2026

Problem

Token deployment endpoints lacked subscription tier enforcement, preventing revenue generation. Idempotency implementation allowed bypassing business logic by reusing keys with different request parameters.

Changes

Subscription enforcement on token deployments

  • Extended SubscriptionTierLimits with MaxTokenDeployments and tracking methods
  • Created TokenDeploymentSubscriptionAttribute filter validating tier limits before deployment
  • Applied to all 11 token creation endpoints (ERC20, ASA, ARC3, ARC200, ARC1400)
  • Tier limits: Free (3), Basic (10), Premium (50), Enterprise (unlimited)
[TokenDeploymentSubscription]
[IdempotencyKey]
[HttpPost("erc20-mintable/create")]
public async Task<IActionResult> ERC20MintableTokenCreate([FromBody] ERC20MintableTokenDeploymentRequest request)

Returns 402 Payment Required when limit reached with structured error response containing current tier, usage, and upgrade path.

Idempotency security fix

  • Added SHA256 hashing of request parameters to idempotency records
  • Validates cached requests match current request parameters
  • Returns 400 Bad Request with IDEMPOTENCY_KEY_MISMATCH when key reused with different params
  • Fixed deployment recording to check HTTP status code instead of result type
  • Added exception logging (was silently catching)
private string ComputeRequestHash(ActionExecutingContext context, IDictionary<string, object?> arguments)
{
    var json = JsonSerializer.Serialize(arguments, ...);
    using var sha256 = SHA256.Create();
    return Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(json)));
}

Test coverage

Added 18 unit tests covering subscription enforcement (10) and idempotency security (8). CodeQL scan clean.

Known limitations

Deployment counts stored in-memory (acceptable for MVP, documented for future persistence).

Original prompt

This section details on the original issue you should resolve

<issue_title>Backend MVP stabilization: Arc76 auth, subscription enforcement, and network metadata reliability</issue_title>
<issue_description>## Summary

Stabilize the backend API to support the MVP subscription business model and SaaS-style authentication by ensuring reliable API connectivity, subscription enforcement, and consistent network metadata delivery. This issue focuses on making the backend dependable for production workloads and for the frontend SaaS experience: Arc76 email/password authentication workflows must be supported, Arc14 secure backend communication must be enforced, subscription status must be checked on relevant API endpoints, and a unified network metadata response should be delivered for production networks and testnets. The goal is to remove backend instability as an MVP blocker and create a trustworthy foundation for the paid platform.

Business Value

Revenue cannot be generated without a functioning backend that enforces subscription plans and guarantees consistent API behavior. The roadmap makes it clear that the subscription system is not operational, which means the business cannot sell its core product. Reliable subscription enforcement and authentication are the gatekeepers for all premium functionality. If the backend returns inconsistent results or fails to validate subscription status, customers will lose trust, and the enterprise market will view the platform as non-compliant.

In regulated asset issuance, trust and auditability are critical. Customers will not deploy regulated tokens if the platform cannot reliably confirm who is authenticated, whether the user has access, and which network is being used for deployment. A stable backend provides the data integrity necessary for compliance. It also reduces support costs and engineering time spent on firefighting outages or inconsistent behavior, freeing the team to move toward enterprise compliance milestones.

Additionally, when network metadata is consistent and documented, the frontend can confidently prioritize mainnet networks without guesswork. This reduces the time to market for production deployments and helps the product leadership communicate a clear “production ready” message to customers. Backend stability therefore directly improves conversion, retention, and the ability to deliver on the revenue targets.

Product overview

See the product vision and roadmap for context: https://raw.githubusercontent.com/scholtz/biatec-tokens/refs/heads/main/business-owner-roadmap.md

Scope

In scope

  1. Authentication and Arc76/Arc14 support

    • Ensure Arc76 email/password authentication is fully supported in API flows and creates self-custody accounts reliably.
    • Ensure Arc14 secure backend communication is enforced, including validation of authentication payloads and consistent error responses for invalid or expired sessions.
  2. Subscription enforcement and authorization

    • Implement subscription tier validation for token creation, deployment, and any premium API endpoints.
    • Ensure the backend checks subscription state before executing high-cost or compliance-sensitive actions.
    • Provide clear error responses and error codes for subscription failures so the frontend can present appropriate UX messaging.
  3. API reliability and health monitoring

    • Introduce explicit health checks for critical dependencies and respond with a clear error structure when unavailable.
    • Improve error handling so that API responses are consistent and predictable under failure scenarios.
    • Add telemetry or logging that highlights failed authentication, subscription, and deployment attempts.
  4. Network metadata consistency

    • Ensure the network metadata endpoint returns prioritized mainnet networks and consistent identifiers for Algorand and EVM chains.
    • Validate that the recommended networks list aligns with the product vision and frontend UX expectations.
  5. Integration readiness

    • Ensure that frontend integration can rely on consistent DTOs and response payloads for authentication, subscription, and network metadata.
    • Provide clear API documentation for the updated endpoints.

Out of scope

  • Frontend UI changes and wording updates (handled in frontend issue).
  • New blockchain protocol integrations beyond the current set.
  • Advanced enterprise compliance tooling (phase 2 and beyond).

Technical approach and dependencies

  • Build on existing authentication services rather than replacing them, ensuring minimal disruption to current flows.
  • Coordinate with subscription system implementation to validate billing state in the API.
  • Use existing configuration patterns to define network metadata, ensuring mainnet networks are clearly labeled.
  • Ensure that any new middleware or guards are properly unit tested and integrated into the API pipeline.

Acceptance Criteria

  1. **Arc76 and Arc14 au...

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Stabilize backend API for MVP subscription model Backend stabilization: subscription enforcement and idempotency security fix Feb 3, 2026
Copilot AI requested a review from ludovit-scholtz February 3, 2026 23:49
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

✅ CI checks passed!

📄 OpenAPI specification generated and available as workflow artifact: openapi-specification

1 similar comment
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

✅ CI checks passed!

📄 OpenAPI specification generated and available as workflow artifact: openapi-specification

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

Test Results

1 100 tests  +18   1 097 ✅ +18   32s ⏱️ +2s
    1 suites ± 0       3 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit 2f1ed90. ± Comparison against base commit 6c905ad.

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

✅ CI checks passed!

📄 OpenAPI specification generated and available as workflow artifact: openapi-specification

2 similar comments
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

✅ CI checks passed!

📄 OpenAPI specification generated and available as workflow artifact: openapi-specification

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

✅ CI checks passed!

📄 OpenAPI specification generated and available as workflow artifact: openapi-specification

@ludovit-scholtz ludovit-scholtz marked this pull request as ready for review February 4, 2026 01:15
Copilot AI review requested due to automatic review settings February 4, 2026 01:15
@ludovit-scholtz ludovit-scholtz merged commit 9d34c8d into master Feb 4, 2026
6 checks passed
@ludovit-scholtz ludovit-scholtz deleted the copilot/stabilize-backend-api branch February 4, 2026 01:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds subscription-based enforcement for token deployments and fixes a critical security vulnerability in the idempotency implementation. The changes enable the MVP subscription business model by preventing free users from deploying unlimited tokens and fixing a bypass vulnerability where attackers could reuse idempotency keys with different parameters to circumvent business logic.

Changes:

  • Added subscription tier enforcement to 11 token deployment endpoints (ERC20, ASA, ARC3, ARC200, ARC1400) with tiered limits: Free (3), Basic (10), Premium (50), Enterprise (unlimited)
  • Fixed idempotency security vulnerability by hashing and validating request parameters, preventing idempotency key reuse with different request data
  • Added 18 comprehensive unit tests covering subscription enforcement and idempotency security scenarios

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
BiatecTokensApi/Filters/TokenDeploymentSubscriptionAttribute.cs New action filter implementing subscription tier validation and deployment counting for token creation endpoints
BiatecTokensApi/Filters/IdempotencyAttribute.cs Enhanced with SHA256 request parameter hashing and mismatch detection to prevent security bypass
BiatecTokensApi/Services/SubscriptionTierService.cs Extended with token deployment tracking methods (CanDeployTokenAsync, RecordTokenDeploymentAsync, GetTokenDeploymentCountAsync)
BiatecTokensApi/Services/Interface/ISubscriptionTierService.cs Added interface methods for token deployment enforcement
BiatecTokensApi/Models/Subscription/SubscriptionTier.cs Added MaxTokenDeployments and TokenDeploymentEnabled properties to tier configuration
BiatecTokensApi/Controllers/TokenController.cs Applied TokenDeploymentSubscription attribute to all 11 token deployment endpoints
BiatecTokensTests/TokenDeploymentSubscriptionTests.cs Comprehensive unit tests for subscription enforcement covering all tiers, authentication, error responses, and recording behavior
BiatecTokensTests/IdempotencySecurityTests.cs Unit tests validating idempotency security including parameter mismatch detection, cache behavior, and header validation
BiatecTokensApi/doc/documentation.xml Updated XML documentation for new filters, methods, and properties

Comment on lines +172 to +228
public async Task<bool> CanDeployTokenAsync(string userAddress)
{
if (string.IsNullOrWhiteSpace(userAddress))
{
_logger.LogWarning("CanDeployTokenAsync called with null or empty userAddress");
return false;
}

var tier = await GetUserTierAsync(userAddress);
var limits = SubscriptionTierConfiguration.GetTierLimits(tier);

if (!limits.TokenDeploymentEnabled)
{
_logger.LogWarning(
"Token deployment not enabled for user {UserAddress} with tier {Tier}",
userAddress, tier);
return false;
}

// Unlimited deployments (-1) always allowed
if (limits.MaxTokenDeployments == -1)
{
_logger.LogDebug(
"Token deployment allowed for user {UserAddress} (unlimited tier {Tier})",
userAddress, tier);
return true;
}

var currentCount = await GetTokenDeploymentCountAsync(userAddress);
var canDeploy = currentCount < limits.MaxTokenDeployments;

_logger.LogDebug(
"Token deployment check for user {UserAddress}: {CanDeploy} (Tier: {Tier}, Current: {Current}, Max: {Max})",
userAddress, canDeploy, tier, currentCount, limits.MaxTokenDeployments);

return canDeploy;
}

/// <inheritdoc/>
public Task<bool> RecordTokenDeploymentAsync(string userAddress)
{
if (string.IsNullOrWhiteSpace(userAddress))
{
_logger.LogWarning("RecordTokenDeploymentAsync called with null or empty userAddress");
return Task.FromResult(false);
}

var normalizedAddress = userAddress.ToUpperInvariant();
_tokenDeploymentCounts.AddOrUpdate(normalizedAddress, 1, (key, oldValue) => oldValue + 1);

var newCount = _tokenDeploymentCounts[normalizedAddress];
_logger.LogInformation(
"Recorded token deployment for user {UserAddress}. New count: {Count}",
userAddress, newCount);

return Task.FromResult(true);
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential race condition between CanDeployTokenAsync (line 200-201) and RecordTokenDeploymentAsync (line 220). If two concurrent requests check CanDeployTokenAsync when count is at limit-1, both will pass validation, then both will increment the counter, allowing one request to exceed the limit. For example, with a limit of 3: Request A checks (count=2, passes), Request B checks (count=2, passes), Request A increments (count=3), Request B increments (count=4, exceeds limit). While this is documented as acceptable for MVP with in-memory storage, consider adding a comment explaining this known limitation or using a compare-and-swap pattern with AddOrUpdate to prevent exceeding limits.

Copilot uses AI. Check for mistakes.
// Key: user address, Value: subscription tier
private readonly ConcurrentDictionary<string, SubscriptionTier> _userTiers;

// In-memory storage for token deployment counts
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions user tier assignments but the deployment counts dictionary below it is not mentioned in inline comments. While the class-level documentation exists outside the diff, it would be helpful to add a comment here noting that deployment counts are also in-memory and will be lost on restart, which is a known MVP limitation mentioned in the PR description.

Suggested change
// In-memory storage for token deployment counts
// In-memory storage for token deployment counts.
// Note: counts are not persisted and will be reset on service restart (MVP limitation).

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +47
if (tierService == null || logger == null)
{
logger?.LogWarning("TokenDeploymentSubscriptionAttribute: Required services not available");
await next();
return;
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When required services (tierService or logger) are not available, the filter proceeds with the request without validation. This follows the same pattern as SubscriptionTierValidationAttribute (line 40-45 in SubscriptionTierValidationAttribute.cs), but represents a fail-open security posture that could allow unauthorized deployments if dependency injection is misconfigured. Consider changing this to fail-closed by returning an error response (500 Internal Server Error) instead of proceeding, which would prevent deployments when the subscription service is unavailable and make misconfigurations more obvious.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +85
context.Result = new BadRequestObjectResult(new
{
success = false,
errorCode = "IDEMPOTENCY_KEY_MISMATCH",
errorMessage = "The provided idempotency key has been used with different request parameters. Please use a unique key for this request or reuse the same parameters.",
correlationId = context.HttpContext.TraceIdentifier
});
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response uses an anonymous object instead of the ApiErrorResponse class, which is inconsistent with the rest of the codebase. The TokenDeploymentSubscriptionAttribute (in the same PR) and other filters like SubscriptionTierValidationAttribute use ApiErrorResponse for structured error responses. This inconsistency makes it harder to handle errors uniformly on the client side and breaks the established pattern for error handling. Consider replacing the anonymous object with an ApiErrorResponse instance that includes all standard fields like Success, ErrorCode, ErrorMessage, RemediationHint, Timestamp, CorrelationId, and Path.

Copilot uses AI. Check for mistakes.
context.Result = new BadRequestObjectResult(new
{
success = false,
errorCode = "IDEMPOTENCY_KEY_MISMATCH",
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error code "IDEMPOTENCY_KEY_MISMATCH" is hardcoded as a string literal instead of being defined as a constant in the ErrorCodes class. All other error codes in the codebase are defined in BiatecTokensApi/Models/ErrorCodes.cs (e.g., ErrorCodes.SUBSCRIPTION_LIMIT_REACHED, ErrorCodes.UNAUTHORIZED). This hardcoded string breaks the established pattern and makes the code harder to maintain and test. Add IDEMPOTENCY_KEY_MISMATCH as a constant in the ErrorCodes class and reference it using ErrorCodes.IDEMPOTENCY_KEY_MISMATCH.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
[SetUp]
public void Setup()
{
_mockLogger = new Mock<ILogger<IdempotencyKeyAttribute>>();
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static _cache field in IdempotencyKeyAttribute can cause test isolation issues because the cache is shared across all test instances. Tests like Idempotency_SameKeyAndParameters_ReturnsCachedResponse add entries to the cache that persist for subsequent tests. While CleanupExpiredEntries() removes expired entries, it only runs probabilistically (1% of the time) and may not clean up between tests. This can lead to non-deterministic test behavior where test execution order affects results. Consider adding a method to clear the cache for testing purposes or making the tests use unique idempotency keys that won't collide.

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +130
// Note: In C#, anonymous objects with different property order create different types
// So we need to test with a dictionary instead
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "So we need to test with a dictionary instead", but the test continues using anonymous objects with different property orders. While the test is functionally correct (different property orders will serialize to different JSON and produce different hashes), the comment is misleading. Either update the comment to explain that JSON serialization preserves property order, making anonymous objects suitable for this test, or actually use a Dictionary to make the test match the comment's intention.

Suggested change
// Note: In C#, anonymous objects with different property order create different types
// So we need to test with a dictionary instead
// Note: In C#, anonymous objects with different property orders are different types,
// and JSON serialization preserves property order. Using anonymous objects with
// reordered properties is therefore sufficient to verify that different parameter
// orders produce different idempotency hashes.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backend MVP stabilization: Arc76 auth, subscription enforcement, and network metadata reliability

2 participants