Skip to content

Commit

Permalink
BCI-3343: Add starknet gas price EA (#3311)
Browse files Browse the repository at this point in the history
  • Loading branch information
augustbleeds authored Jun 4, 2024
1 parent 5419d57 commit a9edfe2
Show file tree
Hide file tree
Showing 29 changed files with 387 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-crews-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/starknet-gas-price-adapter': major
---

First version of starknet gas price adapter
5 changes: 5 additions & 0 deletions .changeset/tiny-terms-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/starknet-gas-price-adapter': minor
---

lint changes
21 changes: 21 additions & 0 deletions .pnp.cjs

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

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
50 changes: 50 additions & 0 deletions packages/sources/starknet-gas-price/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# STARKNET_GAS_PRICE

![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/starknet-gas-price/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 |
| :-------: | :-------------------: | :---------------------------------------------------------------------------------------: | :----: | :-----: | :------: |
|| STARKNET_RPC_URL | The RPC URL used to fetch the gas price | string | | |
| | BACKGROUND_EXECUTE_MS | The amount of time the background execute should sleep before performing the next request | string | | `10_000` |

---

## 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 | [gasprice](#gasprice-endpoint) | `gasprice` |

## Gasprice Endpoint

`gasprice` is the only supported name for this endpoint.

### Input Params

There are no input parameters for this endpoint.

### Example

Request:

```json
{
"data": {
"endpoint": "gasprice"
}
}
```

---

MIT License
41 changes: 41 additions & 0 deletions packages/sources/starknet-gas-price/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@chainlink/starknet-gas-price-adapter",
"version": "0.0.0",
"description": "Chainlink starknet-gas-price adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"starknet-gas-price"
],
"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": {
"@types/jest": "27.5.2",
"@types/node": "16.18.96",
"nock": "14.0.0-beta.6",
"typescript": "5.0.4"
},
"dependencies": {
"@chainlink/external-adapter-framework": "0.33.8",
"starknet": "6.9.0",
"tslib": "2.4.1"
}
}
16 changes: 16 additions & 0 deletions packages/sources/starknet-gas-price/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
STARKNET_RPC_URL: {
description: 'RPC Url For Starknet',
type: 'string',
required: true,
sensitive: true,
},
BACKGROUND_EXECUTE_MS: {
description:
'The amount of time the background execute should sleep before performing the next request',
type: 'number',
default: 10_000,
},
})
29 changes: 29 additions & 0 deletions packages/sources/starknet-gas-price/src/endpoint/gasprice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { gasPriceTransport } from '../transport/gasprice'

export const inputParameters = new InputParameters({})

// Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type
export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: {
Data: {
result: string
}
Result: string
}
Settings: typeof config.settings
}

export const endpoint = new AdapterEndpoint({
// Endpoint name
name: 'gasprice',
// Alternative endpoint names for this endpoint
aliases: ['gas_price'],
// Transport handles incoming requests, data processing and communication for this endpoint
transport: gasPriceTransport,
// Supported input parameters for this endpoint
inputParameters,
})
1 change: 1 addition & 0 deletions packages/sources/starknet-gas-price/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as gasprice } from './gasprice'
17 changes: 17 additions & 0 deletions packages/sources/starknet-gas-price/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
import { config } from './config'
import { gasprice } from './endpoint'

export const adapter = new Adapter({
//Requests will direct to this endpoint if the `endpoint` input parameter is not specified.
defaultEndpoint: gasprice.name,
// Adapter name
name: 'STARKNET_GAS_PRICE',
// Adapter configuration (environment variables)
config,
// List of supported endpoints
endpoints: [gasprice],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
91 changes: 91 additions & 0 deletions packages/sources/starknet-gas-price/src/transport/gasprice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { BaseEndpointTypes, inputParameters } from '../endpoint/gasprice'
import { RpcProvider, num, PendingBlock } from 'starknet'
import { AdapterCustomError } from '@chainlink/external-adapter-framework/validation/error'

const logger = makeLogger('Starknet Gas Price')

export type GasPriceTransportTypes = BaseEndpointTypes

type RequestParams = typeof inputParameters.validated

export class GasPriceTransport extends SubscriptionTransport<GasPriceTransportTypes> {
provider!: RpcProvider

async initialize(
dependencies: TransportDependencies<GasPriceTransportTypes>,
adapterSettings: GasPriceTransportTypes['Settings'],
endpointName: string,
transportName: string,
): Promise<void> {
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
this.provider = new RpcProvider({
nodeUrl: adapterSettings.STARKNET_RPC_URL,
})
}

async backgroundHandler(
context: EndpointContext<GasPriceTransportTypes>,
entries: RequestParams[],
) {
await Promise.all(entries.map(async (param) => this.handleRequest(param)))
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
}

async handleRequest(param: RequestParams) {
let response: AdapterResponse<GasPriceTransportTypes['Response']>
try {
response = await this._handleRequest()
} catch (e) {
logger.error(e)
const errorMessage = e instanceof Error ? e.message : 'Unknown error occurred'
response = {
statusCode: 502,
errorMessage,
timestamps: {
providerDataRequestedUnixMs: 0,
providerDataReceivedUnixMs: 0,
providerIndicatedTimeUnixMs: undefined,
},
}
}
await this.responseCache.write(this.name, [{ params: param, response }])
}

async _handleRequest(): Promise<AdapterResponse<GasPriceTransportTypes['Response']>> {
const providerDataRequestedUnixMs = Date.now()
let block: PendingBlock
try {
block = await this.provider.getBlock('pending')
} catch (e) {
throw new AdapterCustomError({
statusCode: 502,
message: `RPCProvider GetBlock Failed: ${e}`,
})
}

const result = num.hexToDecimalString(block.l1_gas_price.price_in_fri)

return {
data: {
result,
},
statusCode: 200,
result,
timestamps: {
providerDataRequestedUnixMs,
providerDataReceivedUnixMs: Date.now(),
providerIndicatedTimeUnixMs: undefined,
},
}
}

getSubscriptionTtlFromConfig(adapterSettings: GasPriceTransportTypes['Settings']): number {
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
}
}

export const gasPriceTransport = new GasPriceTransport()
4 changes: 4 additions & 0 deletions packages/sources/starknet-gas-price/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"requests": [{}]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as nock from 'nock'
import {
TestAdapter,
setEnvVariables,
} from '@chainlink/external-adapter-framework/util/testing-utils'
import { RpcProvider, PendingBlock } from 'starknet'

describe('execute', () => {
let spy: jest.SpyInstance
let testAdapter: TestAdapter
let oldEnv: NodeJS.ProcessEnv

beforeAll(async () => {
oldEnv = JSON.parse(JSON.stringify(process.env))
process.env.BACKGROUND_EXECUTE_MS = '0'
process.env.STARKNET_RPC_URL = 'https://test-rpc-url-starknet.com'

const mockDate = new Date('2001-01-01T11:11:11.111Z')
spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime())

const adapter = (await import('./../../src')).adapter
adapter.rateLimiting = undefined
testAdapter = await TestAdapter.startWithMockedCache(adapter, {
testAdapter: {} as TestAdapter<never>,
})
})

afterAll(async () => {
setEnvVariables(oldEnv)
await testAdapter.api.close()
nock.restore()
nock.cleanAll()
spy.mockRestore()
})

describe('gasprice endpoint', () => {
it('should return success', async () => {
// mock the starknet.js provider directly
jest.spyOn(RpcProvider.prototype, 'getBlock').mockResolvedValue({
status: 'PENDING',
l1_da_mode: 'BLOB',
l1_data_gas_price: { price_in_fri: '0x2daa35d3076ec4', price_in_wei: '0x3bde2d6cc47' },
l1_gas_price: { price_in_fri: '0x2d7e8782b50', price_in_wei: '0x3ba4e91d' },
parent_hash: '0x5c14708b125390d092c83f2561ed6278fce3c16540d0b9429b00eff32bdc7c2',
sequencer_address: '0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8',
starknet_version: '0.13.1.1',
timestamp: 1717104546,
transactions: [
'0x7fb1a6ab95d283a56cd0d30f0fdfe4eed15543da9529ede1698a8b1b0bb57e1',
'0x460809d209b4174fb005c76a210c86d7d50d1b4209df935b8d2ce3bbafd19d5',
'0x70e841662b27cffdbf4c0ce1446d8ff42f843a8424ef6e6fb90c5c0a4e2bfd4',
'0xd330e47ca906e59c597f55dcc647aff2ddb85936b203a6fd3d98f09c5d6728',
'0x782e010e65ea252a86ee7d4380c64d6c294c759e2e803d454325428c07ea37',
'0x679c2c91d6224107d7faddfa9812362e716d18e5c34e5c6eac24199ae272136',
'0x150e4259dd0d7e6c2cbf84420e16af0cb13c1cc8d20f671ff528658ac995ebf',
'0x3283068f2b2fd0fcb5432dff3a0255cc8baece8dc6af6ec6d9f11331844c749',
],
} as unknown as PendingBlock)

const expectedResult = {
data: { result: '3126341413712' }, // price_in_fri: '0x2d7e8782b50' converted to decimal string representation
statusCode: 200,
result: '3126341413712', // price_in_fri: '0x2d7e8782b50' converted to decimal string representation
timestamps: {
providerDataRequestedUnixMs: 978347471111, // the mocked date in ms
providerDataReceivedUnixMs: 978347471111, // the mocked date in ms
},
}

const data = {}
const response = await testAdapter.request(data)
expect(response.statusCode).toBe(200)
// match checks that expected result is a subset of the actual response
expect(response.json()).toMatchObject(expectedResult)
})
})
})
9 changes: 9 additions & 0 deletions packages/sources/starknet-gas-price/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*", "src/**/*.json"],
"exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"]
}
Loading

0 comments on commit a9edfe2

Please sign in to comment.