Skip to content

OPDATA-7578 Add stSLX exchange rate endpoint#5095

Open
magiodev-cll wants to merge 20 commits into
refactor/solana-exchange-rate-sharedfrom
feat/OPDATA-7578-stslx-exchange-rate
Open

OPDATA-7578 Add stSLX exchange rate endpoint#5095
magiodev-cll wants to merge 20 commits into
refactor/solana-exchange-rate-sharedfrom
feat/OPDATA-7578-stslx-exchange-rate

Conversation

@magiodev-cll

@magiodev-cll magiodev-cll commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Closes

OPDATA-7578

Description

Adds a stslx-exchange-rate endpoint to the existing solana-functions adapter for the Solstice stSLX-SLX exchange-rate feed.

Changes

  • Register a new stslx-exchange-rate endpoint in solana-functions.
  • Derive the GLAM vault PDA and canonical SLX base-asset ATA for stSLX.
  • Read raw SPL account data for SLX mint, stSLX mint, and the SLX base-asset ATA.
  • Compute the normalized fixed-point exchange rate using bigint math and return 18-decimal integer strings.
  • Add unit and integration coverage plus a changeset.

Steps to Test

  1. yarn prettier --check "packages/sources/solana-functions/src/endpoint/stslx-exchange-rate.ts" "packages/sources/solana-functions/src/transport/stslx-exchange-rate.ts" "packages/sources/solana-functions/test/unit/stslx-exchange-rate.test.ts" "packages/sources/solana-functions/test/integration/stslx-exchange-rate.test.ts"
  2. yarn workspace @chainlink/solana-functions-adapter build
  3. yarn eslint "packages/sources/solana-functions/src/transport/stslx-exchange-rate.ts" "packages/sources/solana-functions/src/endpoint/stslx-exchange-rate.ts" "packages/sources/solana-functions/test/unit/stslx-exchange-rate.test.ts" "packages/sources/solana-functions/test/integration/stslx-exchange-rate.test.ts"
  4. yarn tsc -p "packages/sources/solana-functions/tsconfig.test.json"
  5. yarn test "packages/sources/solana-functions/test" --runInBand

Quality Assurance

  • No environment variables changed; no infra-k8s update needed.
  • No environment variables changed; no adapter-secrets update needed.
  • No test-payload.json exists under packages/sources/solana-functions; unit and integration tests were added instead.
  • The branch naming follows Jira/git flow: feat/OPDATA-7578-stslx-exchange-rate.
  • This is related to one Jira story: OPDATA-7578.
  • Types avoid unsafe any/disable patterns.
  • Unit and integration tests cover the new endpoint behavior.

Automated EA Review and Live Evidence

  • Full redacted output from the experimental ea-review static + dynamic harness was posted in this PR comment: OPDATA-7578 Add stSLX exchange rate endpoint #5095 (comment)
  • Live endpoint test was run against https://api.mainnet-beta.solana.com.
  • Live stslx-exchange-rate response returned statusCode: 200, result: 1009061601987286399, computedResult: 1009061601987286399, and boundsApplied: false.

@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5113652

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@chainlink/solana-functions-adapter Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/sources/solana-functions/src/transport/stslx-exchange-rate.ts Outdated
Comment thread packages/sources/solana-functions/src/transport/stslx-exchange-rate.ts Outdated
@magiodev-cll magiodev-cll requested a review from jcly99 June 17, 2026 11:26
@magiodev-cll

magiodev-cll commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Spawning the local server:

➜  external-adapters-js git:(feat/OPDATA-7578-stslx-exchange-rate) ✗ RPC_URL=https://api.mainnet-beta.solana.com/ \
EA_HOST=127.0.0.1 \
EA_PORT=18080 \
METRICS_ENABLED=false \
yarn workspace @chainlink/solana-functions-adapter server:dist

{"level":"warn","time":1781695665025,"isoTime":"2026-06-17T11:27:45.025Z","pid":85088,"hostname":"MB-JYCTKQD9RJ","layer":"Adapter","msg":"METRICS_ENABLED has been set to false. Metrics should not be disabled in a production environment."}
(node:85088) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
{"level":"info","time":1781695665025,"isoTime":"2026-06-17T11:27:45.025Z","pid":85088,"hostname":"MB-JYCTKQD9RJ","layer":"CacheFactory","msg":"Using \"local\" cache."}
{"level":"info","time":1781695665058,"isoTime":"2026-06-17T11:27:45.058Z","pid":85088,"hostname":"MB-JYCTKQD9RJ","layer":"Main","msg":"Starting background execution loop"}
{"level":"info","time":1781695665062,"isoTime":"2026-06-17T11:27:45.062Z","pid":85088,"hostname":"MB-JYCTKQD9RJ","layer":"Main","msg":"Listening on port 18080"}

Testing it:

➜  external-adapters-js git:(feat/OPDATA-7578-stslx-exchange-rate) ✗ curl -sS -X POST http://127.0.0.1:18080/ \
  -H 'Content-Type: application/json' \
  --data '{
    "data": {
      "endpoint": "stslx-exchange-rate",
      "glamStateAddress": "5E2scHi8LyZAqZeVHnXLeFhwoePxD2CTdSruWmjgVEoB",
      "minRate": "950000000000000000",
      "maxRate": "1050000000000000000"
    }
  }' | jq
{
  "data": {
    "result": "1006963459819162228",
    "computedResult": "1006963459819162228",
    "decimals": 18,
    "minRate": "950000000000000000",
    "maxRate": "1050000000000000000",
    "boundsApplied": false
  },
  "statusCode": 200,
  "result": "1006963459819162228",
  "timestamps": {
    "providerDataRequestedUnixMs": 1781695670121,
    "providerDataReceivedUnixMs": 1781695670318
  }
}
➜  external-adapters-js git:(feat/OPDATA-7578-stslx-exchange-rate) ✗

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@magiodev-cll magiodev-cll marked this pull request as ready for review June 17, 2026 13:03
@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from d416f82 to 9d0ad80 Compare June 18, 2026 07:59
@magiodev-cll

Copy link
Copy Markdown
Contributor Author

Automated EA Review Harness Output + Live API Evidence

PR: #5095
Branch: feat/OPDATA-7578-stslx-exchange-rate
Commit tested: 9d0ad80a3
Adapter: @chainlink/solana-functions-adapter
Endpoint: stslx-exchange-rate

Static Harness Command

EA_REVIEW_NO_TELEMETRY=1 node <ea-review>/dist/cli.js scan \
  -a solana-functions \
  -r <external-adapters-js> \
  -p ci-fast \
  --severity critical,high \
  -f markdown

Static Harness Output

EA Review Results

Profile: ci-fast | Adapters: solana-functions | Duration: 89ms

Summary

Metric Count
Total 70
Passed 28
Failed 42
Critical 5
High 37

solana-functions

🔴 DATA-002 NaN/Infinity Result `src/transport/eusx-price.ts:90`

Severity: critical
Message: Division operation without NaN/Infinity guard; add isFinite() check or guard before computing
Why: NaN or Infinity propagating as a price result causes the Chainlink node to submit invalid data on-chain, potentially triggering incorrect liquidations or contract behavior
Fix: Add Number.isNaN() and Number.isFinite() checks after numeric operations before returning results

numerator / denominator
🔴 DATA-002 NaN/Infinity Result `src/transport/sanctum-infinity.ts:76`

Severity: critical
Message: Division operation without NaN/Infinity guard; add isFinite() check or guard before computing
Why: NaN or Infinity propagating as a price result causes the Chainlink node to submit invalid data on-chain, potentially triggering incorrect liquidations or contract behavior
Fix: Add Number.isNaN() and Number.isFinite() checks after numeric operations before returning results

Number(totalPoolValueString.result) / Number(totalPoolTokenSupplyString.result)
🔴 DATA-002 NaN/Infinity Result `src/transport/sanctum-infinity.ts:76`

Severity: critical
Message: Number() call without NaN validation; add Number.isNaN() check or return null/default if invalid
Why: NaN or Infinity propagating as a price result causes the Chainlink node to submit invalid data on-chain, potentially triggering incorrect liquidations or contract behavior
Fix: Add Number.isNaN() and Number.isFinite() checks after numeric operations before returning results

Number(totalPoolValueString.result)
🔴 DATA-002 NaN/Infinity Result `src/transport/sanctum-infinity.ts:76`

Severity: critical
Message: Number() call without NaN validation; add Number.isNaN() check or return null/default if invalid
Why: NaN or Infinity propagating as a price result causes the Chainlink node to submit invalid data on-chain, potentially triggering incorrect liquidations or contract behavior
Fix: Add Number.isNaN() and Number.isFinite() checks after numeric operations before returning results

Number(totalPoolTokenSupplyString.result)
🔴 DATA-002 NaN/Infinity Result `src/transport/stslx-exchange-rate.ts:218`

Severity: critical
Message: Division operation without NaN/Infinity guard; add isFinite() check or guard before computing
Why: NaN or Infinity propagating as a price result causes the Chainlink node to submit invalid data on-chain, potentially triggering incorrect liquidations or contract behavior
Fix: Add Number.isNaN() and Number.isFinite() checks after numeric operations before returning results

(slxBalance * 10n ** BigInt(RESULT_DECIMALS + stslxMint.decimals)) /
      (stslxMint.supply * 10n ** BigInt(slxMint.decimals))
🟠 SEC-007 Prototype Pollution `src/shared/account-reader.ts:16`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

  ): Promise<T> {
    const [pda] = await getProgramDerivedAddress({
      programAddress,
🟠 SEC-007 Prototype Pollution `src/transport/eusx-price.ts:99`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

    const programAddress = params.address as Address
    const [vestingSchedule, yieldPool] = await Promise.all([
      accountReader.fetchAccountInformationByAddressAndSeeds<VestingSchedule>(
🟠 SEC-007 Prototype Pollution `src/transport/extension.ts:54`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

    const extensionData = buffer.slice(offset, offset + extensionLength)
    extensionDataByType[extensionType] = extensionData
    offset += extensionLength
🟠 SEC-007 Prototype Pollution `src/transport/extension.ts:119`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

      for (const baseField of params.baseFields) {
        resultData[baseField.name] = getFieldFromBuffer(baseField, data)
      }
🟠 SEC-007 Prototype Pollution `src/transport/extension.ts:127`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

      for (const extensionField of params.extensionFields) {
        resultData[extensionField.name] = getExtensionField(extensionField, extensionDataByType)
      }
🟠 SEC-007 Prototype Pollution `src/transport/extension.ts:132`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

    if (params.resultDecimals !== undefined) {
      resultData['decimals'] = params.resultDecimals
    }
🟠 SEC-007 Prototype Pollution `src/transport/sanctum-infinity.ts:62`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

    const [totalPoolValueString, totalPoolTokenSupplyString] = await Promise.all([
      fetchFieldFromBufferLayoutStateAccount({
🟠 SEC-007 Prototype Pollution `src/transport/stslx-exchange-rate.ts:128`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

  // GLAM stores token assets in a vault PDA derived from the state account and protocol program.
  const [vaultAddress] = PublicKey.findProgramAddressSync(
    [Buffer.from('vault'), new PublicKey(glamStateAddress).toBuffer()],
🟠 SEC-007 Prototype Pollution `src/transport/stslx-exchange-rate.ts:202`

Severity: high
Message: Detects potential prototype pollution via dynamic property assignment
Why: Prototype pollution can corrupt shared JavaScript objects, potentially altering price data across all adapter responses or bypassing validation logic
Fix: Validate property keys against a whitelist before dynamic assignment; use Object.create(null) for dictionaries

    // divided by stSLX mint supply, normalized by each mint's native decimals.
    const [slxMint, stslxMint, slxBalance] = await Promise.all([
      this.fetchMintInfo(SLX_MINT_ADDRESS, 'SLX mint'),
🟠 CFG-001 Missing Rate Limiting `src/index.ts:1`

Severity: high
Message: Missing rate limiting for http transport; add rateLimiting config to prevent API quota exhaustion
Why: Missing rate limiting on HTTP/REST transports can exhaust API quotas, causing cascading failures. Streaming transports (WebSocket, batch) don't hit per-request API quotas and are exempt.
Fix: Add rateLimiting configuration to HTTP/REST adapters; WebSocket/batch adapters can safely omit this

🟠 DATA-001 Missing Error Response `src/transport/anchor-data.ts:39`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/buffer-layout.ts:29`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/buffer-layout.ts:59`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

    const result = await fetchFieldFromBufferLayoutStateAccount({
      stateAccountAddress: params.stateAccountAddress,
🟠 DATA-001 Missing Error Response `src/transport/eusx-price.ts:61`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  ) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/eusx-price.ts:99`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

    const programAddress = params.address as Address
    const [vestingSchedule, yieldPool] = await Promise.all([
      accountReader.fetchAccountInformationByAddressAndSeeds<VestingSchedule>(
🟠 DATA-001 Missing Error Response `src/transport/extension.ts:80`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/pool-token-rate.ts:31`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/pool-token-rate.ts:61`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

    const { programAddress, data } = await fetchDataFromBufferLayoutStateAccount({
      stateAccountAddress: params.stakePoolAccountAddress,
🟠 DATA-001 Missing Error Response `src/transport/sanctum-infinity.ts:32`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/sanctum-infinity.ts:62`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

    const [totalPoolValueString, totalPoolTokenSupplyString] = await Promise.all([
      fetchFieldFromBufferLayoutStateAccount({
🟠 DATA-001 Missing Error Response `src/transport/stslx-exchange-rate.ts:158`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

  async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
    await Promise.all(entries.map(async (param) => this.handleRequest(param)))
    await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
🟠 DATA-001 Missing Error Response `src/transport/stslx-exchange-rate.ts:202`

Severity: high
Message: Network/API call without try-catch error handling
Why: Unhandled API errors crash the adapter or return undefined results to the Chainlink node, causing failed oracle reports and potential node penalties
Fix: Wrap all network/API calls in try-catch blocks and return proper error responses

    // divided by stSLX mint supply, normalized by each mint's native decimals.
    const [slxMint, stslxMint, slxBalance] = await Promise.all([
      this.fetchMintInfo(SLX_MINT_ADDRESS, 'SLX mint'),
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/anchor-data.ts:56`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/anchor-data.ts:117`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/buffer-layout.ts:46`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/buffer-layout.ts:76`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/eusx-price.ts:78`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/eusx-price.ts:165`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/extension.ts:97`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/extension.ts:142`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/pool-token-rate.ts:48`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/pool-token-rate.ts:92`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/sanctum-infinity.ts:49`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/sanctum-infinity.ts:87`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/stslx-exchange-rate.ts:175`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 ERR-002 Zero/Null Timestamp in Error Response `src/transport/stslx-exchange-rate.ts:241`

Severity: high
Message: Timestamp set to undefined in response — can corrupt cache TTL
Why: Generic error handling without distinguishing error types (network vs parsing vs validation) prevents proper recovery strategies and makes debugging harder
Fix: Always return Date.now() as timestamps in error responses to prevent cache TTL corruption

providerIndicatedTimeUnixMs: undefined
🟠 CHAIN-001 RPC Timeout `src/transport/stslx-exchange-rate.ts:1`

Severity: high
Message: RPC provider configured without explicit timeout — calls can hang indefinitely
Why: RPC calls without timeout can hang indefinitely when a node is unresponsive, blocking the adapter.
Fix: Add explicit timeout to JsonRpcProvider or ethers provider configuration

Dynamic Harness Command

EA_REVIEW_NO_TELEMETRY=1 node <ea-review>/dist/cli.js scan \
  -a solana-functions \
  -r <external-adapters-js> \
  -p dynamic-only \
  --dynamic \
  --dynamic-port 18295 \
  --dynamic-env RPC_URL=https://api.mainnet-beta.solana.com \
  -f markdown

Dynamic Harness Output

EA Review Results

Profile: dynamic-only | Adapters: solana-functions | Duration: 5.5s

Summary

Metric Count
Total 12
Passed 8
Failed 4
High 3
Medium 1

solana-functions

🟠 DYN-002 Runtime Price Validation

Severity: high
Message: Expected status 200, got 404
Why: Price feeds are the core function of an EA — a request that returns non-numeric or non-positive data will poison oracle reports
Fix: Verify the adapter's default endpoint handles standard base/quote pairs and returns a numeric result > 0

🟠 DYN-006 Data Quality Bounds

Severity: high
Message: Non-200 response: 404
Why: Feeding NaN, Infinity, or extreme values into an oracle report can corrupt on-chain price feeds and trigger liquidations or arbitrage exploits
Fix: Add result validation that rejects non-finite, non-positive, or unreasonably large numbers before returning

🟠 DYN-012 Error Recovery

Severity: high
Message: Adapter returned 404 after recovery attempt
Why: If a single bad request leaves the adapter in a broken state, all subsequent requests from every node will fail until the process is restarted
Fix: Ensure error paths do not corrupt shared state — use try/catch around request handling and reset any per-request state on failure

🟡 DYN-009 Response Consistency

Severity: medium
Message: Non-numeric results: undefined, undefined
Why: Large swings between identical requests may indicate race conditions, stale cache issues, or unstable upstream data
Fix: Investigate why consecutive identical requests return significantly different results — check caching and data source stability

Live Endpoint Runtime Command

EA_PORT=18205 \
RPC_URL=https://api.mainnet-beta.solana.com \
METRICS_ENABLED=false \
yarn node -e "require('./dist/index.js').server()"

Live Endpoint Request/Response

{
  "rpcUrl": "https://api.mainnet-beta.solana.com",
  "request": {
    "data": {
      "endpoint": "stslx-exchange-rate",
      "glamStateAddress": "5E2scHi8LyZAqZeVHnXLeFhwoePxD2CTdSruWmjgVEoB",
      "minRate": "950000000000000000",
      "maxRate": "1050000000000000000"
    }
  },
  "response": {
    "statusCode": 200,
    "elapsedMs": 1008,
    "body": {
      "data": {
        "result": "1009079081993244827",
        "computedResult": "1009079081993244827",
        "decimals": 18,
        "minRate": "950000000000000000",
        "maxRate": "1050000000000000000",
        "boundsApplied": false
      },
      "statusCode": 200,
      "result": "1009079081993244827",
      "timestamps": {
        "providerDataRequestedUnixMs": 1782130917209,
        "providerDataReceivedUnixMs": 1782130917432
      }
    }
  }
}

@magiodev-cll

Copy link
Copy Markdown
Contributor Author

Follow-up: EA Review Finding Scope

The automated ea-review harness scans the full existing solana-functions adapter, while this PR only adds/registers the new stslx-exchange-rate endpoint. I split the posted harness findings by whether they are in code introduced by this PR versus pre-existing adapter files.

Static Harness Scope

Static harness failures reported: 42

Classification Count Notes
Findings in the new stSLX transport 8 These are the only findings directly in newly added endpoint code.
Adapter entrypoint heuristic 1 src/index.ts was touched to register the endpoint, but the finding is adapter-wide.
Pre-existing solana-functions files 33 These are outside this PR's endpoint implementation.

Findings directly in new stSLX code:

Rule Location
DATA-002 src/transport/stslx-exchange-rate.ts:218
SEC-007 src/transport/stslx-exchange-rate.ts:128
SEC-007 src/transport/stslx-exchange-rate.ts:202
DATA-001 src/transport/stslx-exchange-rate.ts:158
DATA-001 src/transport/stslx-exchange-rate.ts:202
ERR-002 src/transport/stslx-exchange-rate.ts:175
ERR-002 src/transport/stslx-exchange-rate.ts:241
CHAIN-001 src/transport/stslx-exchange-rate.ts:1

Touched but not specific to the new endpoint:

Rule Location Why it is adapter-wide
CFG-001 src/index.ts:1 This flags adapter-level rate limiting. This PR only touches src/index.ts to register the endpoint.

Pre-existing files with findings:

  • src/transport/eusx-price.ts
  • src/transport/sanctum-infinity.ts
  • src/shared/account-reader.ts
  • src/transport/extension.ts
  • src/transport/anchor-data.ts
  • src/transport/buffer-layout.ts
  • src/transport/pool-token-rate.ts

Dynamic Harness Scope

Dynamic harness failures reported:

  • DYN-002
  • DYN-006
  • DYN-009
  • DYN-012

These are not clearly caused by the new stSLX endpoint. The experimental dynamic harness sends generic/default adapter requests and received 404 / non-numeric responses for value-oriented scenarios. The endpoint-specific live request in the previous comment is the stronger runtime evidence for this PR and returned statusCode: 200 with result == computedResult and boundsApplied: false.

Summary

Most harness findings are from pre-existing solana-functions code. The PR-relevant static findings are limited to the new stslx-exchange-rate transport plus one adapter-wide entrypoint heuristic. The live endpoint test confirms the new endpoint returns a successful response against the actual Solana RPC path.

@magiodev-cll

Copy link
Copy Markdown
Contributor Author

Follow-up: Why the PR-local EA review findings are no-fix

This is a concise rationale for the findings that landed in files introduced or touched by this PR. I am not treating all findings as generic false positives; some are real-ish but adapter-wide or cross-cutting. I do not see a required endpoint code change for this PR.

Finding Scope Decision Rationale
DATA-002 at src/transport/stslx-exchange-rate.ts:218 New stSLX transport No fix The calculation uses bigint, not JS number, so it cannot produce NaN or Infinity. The relevant denominator, stslxMint.supply, is checked for 0n before division. Adding Number.isFinite() would be wrong for this type.
SEC-007 at src/transport/stslx-exchange-rate.ts:128 and :202 New stSLX transport No fix These are tuple/array destructuring patterns from PublicKey.findProgramAddressSync and Promise.all. Prototype pollution requires dynamic object-key writes such as obj[userInput] = value; that is not happening here.
DATA-001 at src/transport/stslx-exchange-rate.ts:158 and :202 New stSLX transport No fix The scanner sees async/RPC calls, but this transport follows the existing solana-functions pattern: backgroundHandler calls handleRequest, and handleRequest wraps _handleRequest in try/catch and writes an adapter error response.
ERR-002 at src/transport/stslx-exchange-rate.ts:175 and :241 New stSLX transport No fix This endpoint has no provider-supplied timestamp. Setting providerIndicatedTimeUnixMs to local wall-clock time would be misleading. The existing adapter uses this same pattern for on-chain reads/errors.
CHAIN-001 at src/transport/stslx-exchange-rate.ts:1 Shared RPC behavior surfaced in new file No fix in this PR This is the one finding I would consider real engineering debt, but it belongs in shared SolanaRpcFactory/adapter config work because every Solana endpoint uses the same RPC factory. A one-off endpoint patch would be inconsistent.
CFG-001 at src/index.ts:1 Adapter entrypoint touched for endpoint registration No fix in this PR The finding is adapter-wide rate-limiting heuristics. This PR only touches src/index.ts to register stslxExchangeRate; it does not introduce a new external HTTP provider quota path.

Dynamic harness note: the DYN-* failures came from generic/default adapter requests returning 404 or non-numeric results. The endpoint-specific live request for stslx-exchange-rate returned HTTP 200 with result == computedResult and boundsApplied: false, so I do not treat those generic dynamic failures as endpoint bugs.

Suggested follow-up outside this PR: if the team wants to address the most legitimate scanner concern, create a separate shared task for explicit Solana RPC timeout support in SolanaRpcFactory.

@mohamed-mehany mohamed-mehany left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review summary

Thanks @magiodev-cll for putting this together.
To be upfront: I went through the logic and I believe while it maybe functional and matches the methodology in OPDATA-7578 (vault SLX balance / stSLX supply, bigint fixed-point, min/max clamp).
The problem is the state of the code, it's quite hard to read and review, and it reads as largely LLM-generated. That's fine as a starting point, but for this to meet Chainlink standards it needs to follow our best practices, reuse the abstractions we already have, and be reliable/maintainable enough for the next person to review and own. Right now it forks its own parallel idiom instead of building on the adapter.

Could you please refactor this and, when you do, explicitly instruct the model to follow best practices, prefer existing abstractions, and not reinvent what the adapter already provides? Also, for large PRs like this one, please use a stronger model (ideally Opus 4.8 or equivalent) to avoid the code smells below. There should be no magic numbers or arbitrary hardcoded values left in the final version; every constant should be named and traceable to a source (IDL, on-chain address, or config).

Correctness issues

  • Non-atomic reads cause the exact read-skew the ticket warns about. The SLX balance and stSLX supply are fetched as three independent getAccountInfo calls via Promise.all, so they can resolve at different slots and produce a transiently wrong rate. Use a single getMultipleAccounts call to read them atomically (same slot). Note the strcUSX PR already does this correctly, please mirror that.
  • Confirm the ATA assumption with Solstice/GLAM. We derive the canonical associated token account for the vault's SLX, whereas the ticket's reference code enumerates getTokenAccountsByOwner. If GLAM ever holds SLX in a non-ATA account, this endpoint breaks. It fails safe today (mint/owner are validated), but the assumption should be confirmed and documented.

Code quality / structure

  • Reuse existing abstractions. The adapter already has SolanaAccountReader and shared/buffer-layout-accounts.ts (typed MintLayout/AccountLayout decode + owner/length checks). This PR re-implements raw RPC reads and buffer slicing with hand-rolled response types (AccountInfoRpcResponse, as casts) instead of extending those. Please build on the shared readers.
  • De-duplicate shared helpers. parseRateBound, getAccountDataBuffer, assertOwnerProgram/assertTokenProgramOwner, the bounds-clamp, and the AccountInfo/MintInfo types are duplicated verbatim with the strcUSX PR. Extract them into src/shared/ so both endpoints share one implementation.
  • Stick to one Solana stack. New code pulls in legacy @solana/web3.js PublicKey and @solana/spl-token helpers, while the rest of the adapter uses the modern @solana/addresses / @solana/rpc stack (getProgramDerivedAddress). Please align with the existing stack rather than mixing both.
  • No hardcoded values / magic numbers. Pull the bounds-clamp out into a single named helper instead of an inline nested ternary, and ensure any constant (decimals, addresses) is a named, documented constant.
  • boundsApplied is computed by string comparison (result !== computedResult); compare the bigint values instead.

@mohamed-mehany

Copy link
Copy Markdown
Collaborator

Suggestion: extract a shared-components PR and stack these on top

Stepping back from the per-PR comments, both PRs (#5095 (stSLX) and #5097 (strcUSX)) and endpoints duplicate a substantial amount of the same code (parseRateBound, getAccountDataBuffer, assertOwnerProgram/assertTokenProgramOwner, the min/max bounds-clamp, the
shared AccountInfo/MintInfo types, and the raw account-read plumbing).

Rather than refactoring each PR independently and reconciling the two copies later, it
would likely be cleaner to:

  1. Open a third PR first that lands the shared building blocks in
    src/shared/, e.g. rate-bound parsing/validation, the bounds-clamp helper,
    account-buffer extraction + owner/length assertions, and (ideally) IDL/typed
    buffer-layout decoding built on the existing SolanaAccountReader /
    buffer-layout-accounts.ts. This PR has no new endpoint and is small and easy
    to review on its own.
  2. Rebase/stack OPDATA-7578 Add stSLX exchange rate endpoint #5095 (stSLX) and OPDATA-7578 Add strcUSX tranche exchange rate endpoint #5097 (strcUSX) on top of it, so each
    endpoint PR shrinks to just its endpoint definition, its decode layout, and its
    rate formula, no duplicated scaffolding.

Benefits:

  • One canonical implementation of the shared logic instead of two copies that can
    drift.
  • Each endpoint PR becomes much smaller and genuinely reviewable.
  • The shared module can be unit-tested once, in isolation.
  • Future Solana exchange-rate endpoints get the same foundation for free.

@mohamed-mehany

Copy link
Copy Markdown
Collaborator

Two follow-ups:

  1. vaultPDA derivation. The vault PDA (and SLX ATA) are derived per-request,
    but for this feed they're constant. Also note the endpoint is half-parameterized:
    glamStateAddress is a request param while the mints/program are hardcoded, so
    it's not actually generic. Either hardcode the resolved vault/ATA as named
    constants with a unit test asserting the derivation matches, or make it fully
    generic (mints + program as params too). Not the current half-and-half.

  2. Reuse. This is essentially "token-account balance / mint supply." The adapter
    already has shared/buffer-layout-accounts.ts (decodes AccountLayout/MintLayout).
    Please reuse it instead of the bespoke fetchMintInfo/fetchBaseAssetBalance.

@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from 9d0ad80 to 76fd8f3 Compare June 24, 2026 12:31
@magiodev-cll magiodev-cll changed the base branch from main to refactor/solana-exchange-rate-shared June 24, 2026 12:31
@danwilliams-cll danwilliams-cll requested a review from Copilot June 24, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment on lines +29 to +33
export const SLX_MINT_ADDRESS = 'SLXdx4BUt2v9uJQNzWqSfzTJ9UKLUDsvxHFMEEdrfgq'
export const STSLX_MINT_ADDRESS = 'GxHksENo754dKj6kv5d2z7ey9KwE7YSRYgRCtoFYd2yq'
export const GLAM_VAULT_ADDRESS = 'GMwdh2jTdTrrhA7dMR7Cc2zC6gV38UePzAXeoFHrXnfH'
export const SLX_TOKEN_ACCOUNT_ADDRESS = '7CssRFNePpnDiCzjRC5kPRDpEJn87JMeDG7s6Gww9CTf'

@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from 36d1ca6 to 781cafa Compare June 24, 2026 15:04
@danwilliams-cll

Copy link
Copy Markdown

Code Review: PR #5095 — stSLX exchange rate: use new shared helpers

Verdict: APPROVE — tight, focused refactoring. Three minor points, none blocking.


What Changed (+ lines only)

Adopts assertTokenProgramOwner, derivePda, and parseSolanaAddress from PR #5128. Adds deriveVaultAddress and deriveSlxTokenAccountAddress as exported helpers. Exports intermediate addresses (GLAM_STATE_ADDRESS, GLAM_PROTOCOL_PROGRAM_ADDRESS, ASSOCIATED_TOKEN_PROGRAM_ADDRESS) that were previously implicit.


Findings

stslx-exchange-rate.ts:L59: [LOW] correctness: parseSolanaAddress(LEGACY_TOKEN_PROGRAM_ADDRESS, 'tokenProgramAddress') validates a known-good constant (TOKEN_PROGRAM_ID.toBase58()). This call can never throw — redundant guard. Pass it directly or cast with address().

stslx-exchange-rate.ts:L51: [LOW] shrink: vaultAddress.toString() — derivePda returns Address which already extends string. No-op. Return vaultAddress directly.

stslx-exchange-rate.ts:L44-61: [LOW] yagni: deriveVaultAddress and deriveSlxTokenAccountAddress are exported but _handleRequest still uses the hardcoded GLAM_VAULT_ADDRESS and SLX_TOKEN_ACCOUNT_ADDRESS constants. If these only exist to verify the hardcoded values in tests, a single test assertion is enough — no need to export them from the transport module.

@danwilliams-cll danwilliams-cll self-requested a review June 24, 2026 16:10
@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from 781cafa to 5d8f0d3 Compare June 24, 2026 16:12
"endpoint": "stslx-exchange-rate",
"minRate": "950000000000000000",
"maxRate": "1050000000000000000"
}

@jcly99 jcly99 Jun 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

consider:

  1. The ability to enable/disable rateBound. For ie: applyBounds(boolean)

@mohamed-mehany

Copy link
Copy Markdown
Collaborator
  1. Reuse miss: inline assertAddressMatches. The two mint/owner checks are hand-written if (slxToken.x !== Y) throw, but OPDATA-7578: Refactor Solana exchange-rate shared utilities #5128 now exports assertAddressMatches. Swap them, it's the same helper OPDATA-7578 Add strcUSX tranche exchange rate endpoint #5097 already uses, and trims the transport.

  2. Those two checks are also largely redundant. A token account's mint and owner are immutable after init, and the derivation test already proves SLX_TOKEN_ACCOUNT_ADDRESS is the canonical ATA of the vault for the SLX mint. So at runtime the decoded mint/owner can't differ (a missing account is already caught by getAccountDataBuffer). If they're not needed, let's remove them.

  3. Status codes: upstream conditions ("zero supply", missing account) throw AdapterInputError with 500. Consistent with the EA, but AdapterDataProviderError would label provider failures correctly in metrics.

  4. Minor response over-echo: minRate/maxRate are echoed back in Data (caller already supplied them), no need to return inputs in the output (unless they're needed somehow).

Comment on lines +29 to +32
export const SLX_MINT_ADDRESS = 'SLXdx4BUt2v9uJQNzWqSfzTJ9UKLUDsvxHFMEEdrfgq'
export const STSLX_MINT_ADDRESS = 'GxHksENo754dKj6kv5d2z7ey9KwE7YSRYgRCtoFYd2yq'
export const GLAM_VAULT_ADDRESS = 'GMwdh2jTdTrrhA7dMR7Cc2zC6gV38UePzAXeoFHrXnfH'
export const SLX_TOKEN_ACCOUNT_ADDRESS = '7CssRFNePpnDiCzjRC5kPRDpEJn87JMeDG7s6Gww9CTf'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

On the hardcoded addresses in the transport (SLX_MINT_ADDRESS, STSLX_MINT_ADDRESS, GLAM_VAULT_ADDRESS, SLX_TOKEN_ACCOUNT_ADDRESS): it works and the derivation test is a good mitigation, but I'd reconsider the approach. Three points:

1. It diverges from this EA's convention. The other endpoints parameterize their addresses via inputParameters with a default rather than hardcoding them in the transport, e.g. eusx-price takes address as a required param with a default, and pool-token-rate/anchor-data take their account addresses as params. Parameterizing with defaults gives the same out-of-the-box behavior plus: the addresses get documented in the generated README, they're visible at the job-spec layer, and they're overridable without a code change. And there's no determinism cost, since all NOPs share the same defaults so the computed rate is identical across operators.

2. Hardcoding makes the feed un-reconfigurable without a release. If Solstice migrates the GLAM vault, redeploys, or the mint changes, this needs a code change + EA release + redeploy instead of a config/job-spec update. That's a slow path for a live price feed.

3. Two of the four constants are derived values and shouldn't be hardcoded at all. GLAM_VAULT_ADDRESS and SLX_TOKEN_ACCOUNT_ADDRESS are deterministic functions of the primary addresses:

  • vault = PDA of ["vault", glamState] under the GLAM program
  • SLX ATA = ATA of (vault, SLX_MINT) under the legacy token program

Hardcoding a derived value adds stale-risk for no benefit, and the derivation test is already computing them to prove the constants, so the code derives them twice. Cleaner to derive vault + ATA once from the source addresses via the shared derivePda (@solana/addresses, no web3.js, no I/O), which removes the constants-to-keep-in-sync and the need for the derivation test.

One caveat on that test: it only proves the constants are internally consistent (given the test's glamState/program/mint, the vault/ATA derive correctly). It does not prove the primary addresses are the correct real-world ones, so real-world correctness still rests on the mainnet smoke test.

Suggested shape: expose the primary addresses (or just glamState) as input params with defaults, and derive vault + ATA at runtime from them via derivePda. If you'd rather keep it locked/hardcoded for a single feed (also fine), then at least only hardcode the source-of-truth addresses (program, mints, glamState) and derive vault + ATA from them rather than hardcoding derived values.

Comment on lines +41 to +61
const providerError = (message: string) =>
new AdapterDataProviderError(
{
message,
statusCode: 502,
},
{
providerDataRequestedUnixMs: 0,
providerDataReceivedUnixMs: 0,
providerIndicatedTimeUnixMs: undefined,
},
)

const asProviderError = <T>(callback: () => T) => {
try {
return callback()
} catch (e: unknown) {
throw providerError(e instanceof Error ? e.message : 'Unknown provider error')
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

providerError is duplicated with #5097. Extract it to src/shared/ rather than defining it in both transports.

Comment on lines +187 to +201
return {
data: {
result,
computedResult,
decimals: RESULT_DECIMALS,
boundsApplied,
},
statusCode: 200,
result,
timestamps: {
providerDataRequestedUnixMs,
providerDataReceivedUnixMs: Date.now(),
providerIndicatedTimeUnixMs: undefined,
},
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add slxBalance + stslxSupply so a wrong rate is debuggable from the response and the two endpoints are consistent.

addressEncoder.encode(address(LEGACY_TOKEN_PROGRAM_ADDRESS)),
addressEncoder.encode(parseSolanaAddress(slxMintAddress, 'slxMintAddress')),
])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Redundant address re-parsing. parseSolanaAddress runs in _handleRequest, then again inside deriveVaultAddress/deriveSlxTokenAccountAddress, then again inside derivePda. Idempotent but wasteful/confusing; consider threading the parsed Address through.

@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from 610e127 to 92f3cfd Compare June 25, 2026 16:45
@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from 02913c8 to fbc893c Compare June 26, 2026 10:12
Comment on lines +39 to +45
const asProviderError = <T>(callback: () => T) => {
try {
return callback()
} catch (e: unknown) {
throw providerError(e instanceof Error ? e.message : 'Unknown provider error')
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Delete asProviderError once #5128 fixes the error taxonomy. It exists only to convert the shared helpers' AdapterInputError 500 into a 502. Once the shared account/decode helpers throw AdapterDataProviderError directly (see #5128), the wrapper and all its call sites (lines 117, 118, 121, 130, 136, 142) collapse to plain calls. This also removes the current inconsistency where strcUSX does not wrap and emits 500 for the same failures.

@magiodev-cll magiodev-cll force-pushed the feat/OPDATA-7578-stslx-exchange-rate branch from fbc893c to 5113652 Compare June 26, 2026 10: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.

5 participants