Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blocksize-capital-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/blocksize-capital-state-adapter': major
---

Add Blocksize Capital State adapter with WebSocket support for real-time aggregated price feeds
22 changes: 22 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
55 changes: 55 additions & 0 deletions packages/sources/blocksize-capital-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# BLOCKSIZE_CAPITAL_STATE

![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/blocksize-capital-state/package.json) ![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet)

This document was generated automatically. Please see [README Generator](../../scripts#readme-generator) for more info.

## Environment Variables

| Required? | Name | Description | Type | Options | Default |
| :-------: | :-------------: | :-------------------------------------: | :----: | :-----: | :---------------------------------------------: |
| ✅ | API_KEY | The Blocksize Capital API key to use | string | | |
| | WS_API_ENDPOINT | WS endpoint for Blocksize-Capital state | string | | `wss://data.blocksize.capital/marketdata/v1/ws` |

---

## Data Provider Rate Limits

There are no rate limits for this adapter.

---

## Input Parameters

| Required? | Name | Description | Type | Options | Default |
| :-------: | :------: | :-----------------: | :----: | :---------------------------------------------------------------------------: | :-----: |
| | endpoint | The endpoint to use | string | [crypto](#price-endpoint), [price](#price-endpoint), [state](#price-endpoint) | `price` |

## Price Endpoint

Supported names for this endpoint are: `crypto`, `price`, `state`.

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :---: | :------------: | :--------------------------------------------: | :----: | :-----: | :-----: | :--------: | :------------: |
| ✅ | base | `coin`, `from` | The symbol of symbols of the currency to query | string | | | | |
| ✅ | quote | `market`, `to` | The symbol of the currency to convert to | string | | | | |

### Example

Request:

```json
{
"data": {
"endpoint": "price",
"base": "CBBTC",
"quote": "USD"
}
}
```

---

MIT License
42 changes: 42 additions & 0 deletions packages/sources/blocksize-capital-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@chainlink/blocksize-capital-state-adapter",
"version": "0.0.0",
"description": "Chainlink blocksize-capital-state adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"blocksize-capital-state"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"url": "https://github.com/smartcontractkit/external-adapters-js",
"type": "git"
},
"license": "MIT",
"scripts": {
"clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo",
"prepack": "yarn build",
"build": "tsc -b",
"server": "node -e 'require(\"./index.js\").server()'",
"server:dist": "node -e 'require(\"./dist/index.js\").server()'",
"start": "yarn server:dist"
},
"devDependencies": {
"@sinonjs/fake-timers": "9.1.2",
"@types/jest": "^29.5.14",
"@types/node": "22.14.1",
"@types/sinonjs__fake-timers": "8.1.5",
"nock": "13.5.6",
"typescript": "5.8.3"
},
"dependencies": {
"@chainlink/external-adapter-framework": "2.7.2",
"tslib": "2.4.1"
}
}
15 changes: 15 additions & 0 deletions packages/sources/blocksize-capital-state/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
API_KEY: {
description: 'The Blocksize Capital API key to use',
type: 'string',
required: true,
sensitive: true,
},
WS_API_ENDPOINT: {
description: 'WS endpoint for Blocksize-Capital state',
type: 'string',
default: 'wss://data.blocksize.capital/marketdata/v1/ws',
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as state } from './state'
28 changes: 28 additions & 0 deletions packages/sources/blocksize-capital-state/src/endpoint/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
PriceEndpoint,
priceEndpointInputParametersDefinition,
} from '@chainlink/external-adapter-framework/adapter'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { stateTransport } from '../transport/state'

export const inputParameters = new InputParameters(priceEndpointInputParametersDefinition, [
{
base: 'CBBTC',
quote: 'USD',
},
])

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: SingleNumberResultResponse
Settings: typeof config.settings
}

export const endpoint = new PriceEndpoint({
name: 'price',
aliases: ['crypto', 'state'],
transport: stateTransport,
inputParameters,
})
13 changes: 13 additions & 0 deletions packages/sources/blocksize-capital-state/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { PriceAdapter } from '@chainlink/external-adapter-framework/adapter'
import { config } from './config'
import { state } from './endpoint'

export const adapter = new PriceAdapter({
name: 'BLOCKSIZE_CAPITAL_STATE',
defaultEndpoint: state.name,
config,
endpoints: [state],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
116 changes: 116 additions & 0 deletions packages/sources/blocksize-capital-state/src/transport/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports'
import { makeLogger } from '@chainlink/external-adapter-framework/util'
import { BaseEndpointTypes } from '../endpoint/state'
import {
blocksizeStateWebsocketOpenHandler,
buildBlocksizeWebsocketTickersMessage,
processStateData,
StateData,
} from './utils'

const logger = makeLogger('StateTransport')

// JSON-RPC 2.0 message structure used by Blocksize Capital WebSocket API
// Used for both authentication and subscription/streaming messages
interface BlocksizeMessage {
jsonrpc: string
id?: number
method?: string
params?: any
result?: any
error?: any
}

interface StateMessage extends BlocksizeMessage {
method: 'state'
params: {
states: StateData[]
}
}

type WSMessage = BlocksizeMessage | StateMessage

export interface TransportTypes extends BaseEndpointTypes {
Provider: {
WsMessage: WSMessage
}
}

type RequestParams = { base: string; quote: string }

// Handle subscription snapshot response
// Provider sends initial snapshot upon successful subscription
function handleSubscriptionSnapshot(message: BlocksizeMessage): any[] {
// Validate snapshot structure
if (!message.result?.snapshot || !Array.isArray(message.result.snapshot)) {
logger.warn(`Invalid snapshot structure for subscription ${message.id}`)
return []
}

logger.info(
`Subscription snapshot received (request ${message.id}): ${message.result.snapshot.length} items`,
)

if (message.result.snapshot.length === 0) {
logger.warn(
`Pair does not exist for subscription ${message.id}. Verify ticker format matches provider requirements or check if asset is supported`,
)
return []
}

// Validate each state data item in snapshot
const validStates = message.result.snapshot.filter((state: any) => {
if (!state || typeof state !== 'object' || !state.base_symbol || !state.quote_symbol) {
logger.warn(`Invalid state data in snapshot: ${JSON.stringify(state)}`)
return false
}
return true
})

return validStates.flatMap((state: StateData) => processStateData(state))
}

// Handle streaming state updates
function handleStreamingUpdates(message: StateMessage): any[] {
logger.debug(JSON.stringify(message))
return message.params.states.flatMap((state: StateData) => processStateData(state))
}

export const stateTransport = new WebSocketTransport<TransportTypes>({
url: (context: EndpointContext<TransportTypes>) => context.adapterSettings.WS_API_ENDPOINT,

handlers: {
message(message: WSMessage) {
if (!message || typeof message !== 'object') return []

// Subscription snapshot
if (message.id !== undefined && message.result?.snapshot) {
return handleSubscriptionSnapshot(message)
}

// Streaming state updates
if (message.method === 'state' && message.params?.states) {
return handleStreamingUpdates(message as StateMessage)
}

return []
},

open: (connection, context) =>
blocksizeStateWebsocketOpenHandler(connection, {
api_key: context.adapterSettings.API_KEY,
}),
},

builders: {
subscribeMessage: (params: RequestParams) => {
const ticker = `${params.base}${params.quote}`.toUpperCase()
return buildBlocksizeWebsocketTickersMessage('state_subscribe', { tickers: [ticker] })
},
unsubscribeMessage: (params: RequestParams) => {
const ticker = `${params.base}${params.quote}`.toUpperCase()
return buildBlocksizeWebsocketTickersMessage('state_unsubscribe', { tickers: [ticker] })
},
},
})
Loading
Loading