Skip to content

Conversation

Copy link

Copilot AI commented Nov 10, 2025

Add Cryptographically Signed Quote Expiry Information

This PR adds optional expiry information to FX quotes to make it easier for clients to understand quote lifetime. The expiry information is cryptographically signed to prevent tampering.

Changes Made:

  • Type changes (common.ts):

    • Restructured expiry information into an optional expiry object containing serverTime and expiresAt (both required when present)
    • Both use ISO 8601 format with millisecond precision
  • Configuration (server.ts):

    • Added quoteTTL?: number | ((request: ConversionInputCanonicalJSON) => number | Promise<number>) to KeetaAnchorFXServerConfig.fx
    • Can be either a static number or a function that receives the conversion request and returns TTL
    • Default: 300000ms (5 minutes, matching message expiry)
    • Maximum: 300000ms (5 minutes) - throws error if exceeded
    • When set, quotes include expiry fields
  • Server-side changes (server.ts):

    • Expiry is now cryptographically signed - included in the signature to prevent tampering
    • Dynamic TTL support - quoteTTL can be a function for request-based TTL determination
    • Module-scoped constants and validation - Extracted MAX_QUOTE_TTL, DEFAULT_QUOTE_TTL constants and validateQuoteTTL() function to avoid duplication
    • createExchange endpoint validates expiry.expiresAt before processing quote
    • Expired quotes rejected with QuoteValidationFailed error
    • Validation occurs before optional validateQuote callback
    • Modified quotes with tampered expiry will fail signature verification
  • Tests (server.test.ts):

    • Integrated expiry tests into existing "FX Server Quote Validation Tests"
    • Uses function-based quoteTTL with captured variable for simplified testing
    • Reuses same server instance by changing the captured TTL variable
    • Tests quote generation with expiry fields
    • Tests actual expiry validation using short TTL (1ms)
    • Tests that expired quotes are properly rejected

Key Features:

Cryptographically signed expiry - Expiry timestamps are included in the quote signature, preventing tampering
Dynamic TTL support - quoteTTL can be a function for context-aware TTL determination based on request properties
Default TTL matching message expiry - Defaults to same timeout as message validation
Validation - Server throws error if quoteTTL exceeds maximum (applies to both static and function-returned values)
Structured expiry data - Optional expiry object with required fields when present
Millisecond precision - ISO 8601 format with full timestamp precision
Simplified testing - Function-based quoteTTL allows reusing server with different TTL values
DRY principle - Module-scoped constants and validation function eliminate code duplication
All tests passing - 50 tests pass, linting clean

Usage Examples:

Static TTL:

const server = new KeetaNetFXAnchorHTTPServer({
  fx: {
    quoteTTL: 30000, // 30 seconds (optional, defaults to message expiry timeout)
    // ... other fx config
  }
  // ... other config
});

Dynamic TTL:

const server = new KeetaNetFXAnchorHTTPServer({
  fx: {
    // Function receives the conversion request for context-aware TTL
    quoteTTL: function(request) {
      // Different TTL based on request properties
      if (request.amount > 1000000n) {
        return 10000; // 10 seconds for large amounts
      }
      return 60000; // 60 seconds for smaller amounts
    },
    // ... other fx config
  }
  // ... other config
});

When quoteTTL is configured, quotes include:

{
  "expiry": {
    "serverTime": "2025-11-10T18:00:00.123Z",
    "expiresAt": "2025-11-10T18:00:30.123Z"
  },
  "signed": {
    "signature": "..." // Signature includes expiry data
  }
}

Note: The expiry information is cryptographically signed, so any attempt to modify the expiry timestamps will cause signature verification to fail.

Fixes #99

Original prompt

This section details on the original issue you should resolve

<issue_title>Add Quote Expiry information </issue_title>
<issue_description>To make it easier for clients to understand the lifetime of a quote, we should add optional expiry information to the quote (as signed data so the FX anchor can validate that).

The expiry information should include:

  • Server time (to serve as a reference clock), with milliseconds level precision
  • Time to live or Expiry Time, with milliseconds level precision

The server, when receiving a quote-based request from a client, should calculate whether the quote is expired and use that as the default basis of rejection for the default implementation of the callback in #98</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copilot AI changed the title [WIP] Add optional expiry information to quotes Add optional quote expiry validation with millisecond precision Nov 10, 2025
Copilot AI requested a review from rkeene November 10, 2025 17:30
Copilot AI and others added 2 commits November 10, 2025 18:12
…e tests

Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
    • Triggering command: node (vitest 2) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 2) (dns block)
    • Triggering command: node (vitest 1) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Add optional quote expiry validation with millisecond precision Add optional quote expiry validation with millisecond precision and 5-minute default Nov 10, 2025
Copilot AI requested a review from rkeene November 10, 2025 18:19
Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 2) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Add optional quote expiry validation with millisecond precision and 5-minute default Add cryptographically signed quote expiry validation with millisecond precision and 5-minute default Nov 10, 2025
Copilot AI requested a review from rkeene November 10, 2025 19:37
Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 2) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from rkeene November 10, 2025 19:55
Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 2) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Add cryptographically signed quote expiry validation with millisecond precision and 5-minute default Add cryptographically signed quote expiry validation with dynamic TTL support Nov 10, 2025
Copilot AI requested a review from rkeene November 10, 2025 20:40
Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
    • Triggering command: node (vitest 2) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 1) (dns block)
    • Triggering command: node (vitest 2) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

this.fx = config.fx;

/* Validate and set quoteTTL with default and maximum */
const maxQuoteTTL = 5 * 60 * 1000; // 5 minutes (message expiry)
Copy link
Member

Choose a reason for hiding this comment

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

@copilot This value is repeated, it should be lifted into the module scope so we don't need to repeat it -- even better might be to have a module scoped function which takes a value and determines if it's an acceptable quoteTTL since that is repeated as well

Copy link
Author

Choose a reason for hiding this comment

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

Refactored in commit bedaf40. Created module-scoped constants MAX_QUOTE_TTL and DEFAULT_QUOTE_TTL (both set to 5 * 60 * 1000) and a validateQuoteTTL() function. This eliminates the duplication of both the value and the validation logic between the constructor and the getQuote route handler.

Co-authored-by: rkeene <5068442+rkeene@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 14, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • keeta.com
    • Triggering command: node (vitest 1) (dns block)
  • rep1.test.network.api.keeta.com
    • Triggering command: node (vitest 2) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from rkeene November 14, 2025 20:45
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.

Add Quote Expiry information

2 participants