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
82 changes: 82 additions & 0 deletions config-chainlink-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# =================================================================
# │ Chainlink Data Streams Transmitter Example │
# =================================================================
#
# Copy this file to 'config.yml' and fill in your specific details.
# This file configures which data feeds to monitor and which on-chain
# contracts to send the data to.

# A list of all unique off-chain Data Streams to subscribe to.
# Find more feed IDs in the Chainlink documentation.
feeds:
- name: 'ETH/USD'
feedId: '0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782'
- name: 'AVAX/USD'
feedId: '0x0003735a076086936550bd316b18e5e27fc4f280ee5b6530ce68f5aad404c796'

# --- Default Global Settings ---

# The default chainId to use for transactions if not specified in a target.
chainId: 43113 # Default to Avalanche Fuji

# The maximum gas limit you are willing to spend on a transaction.
gasCap: '250000'

# Cron expression defining the data update frequency.
# This example runs every 30 seconds.
interval: '*/30 * * * * *'

# The minimum price change percentage to trigger an on-chain update.
# This example is set to 0.1%
priceDeltaPercentage: 0.001

# --- Chain & Verifier Definitions ---

# A list of all supported blockchain networks with their RPC URLs.
chains:
- id: 43113
name: 'Avalanche Fuji Testnet'
currencyName: 'Fuji AVAX'
currencySymbol: 'AVAX'
currencyDecimals: 18
rpc: 'https://api.avax-test.network/ext/bc/C/rpc' # <-- TODO: Replace with your own reliable RPC URL
testnet: true
- id: 421614
name: 'Arbitrum Sepolia'
currencyName: 'Arbitrum Sepolia Ether'
currencySymbol: 'ETH'
currencyDecimals: 18
rpc: 'https://sepolia-rollup.arbitrum.io/rpc' # <-- TODO: Replace with your own reliable RPC URL
testnet: true

# The addresses of the official Chainlink Verifier contracts on each network.
verifierAddresses:
- chainId: 43113
address: '0x2bf612C65f5a4d388E687948bb2CF842FFb8aBB3'
- chainId: 421614
address: '0x2ff010DEbC1297f19579B4246cad07bd24F2488A'

# --- On-Chain Target Configurations ---

# This section defines which smart contracts to call for each feed on each chain.
# You can have multiple targets for the same feed.
targetChains:
- chainId: 43113 # Target is on Avalanche Fuji
targetContracts:
# This configuration sends ETH/USD data to a contract on Fuji
- feedId: '0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782'
# TODO: Replace with the address of your deployed contract on Fuji
address: '0xYourDataStreamsFeedContractOnFuji' # <-- CHANGED
# The name of the function to call on your smart contract
functionName: 'updateReport'
# The arguments the Transmitter should prepare and send to the function
functionArgs: ['reportVersion', 'verifiedReport']
# The ABI for the target function, required to encode the transaction
abi:
- name: 'updateReport'
type: 'function'
stateMutability: 'nonpayable'
inputs:
- { "internalType": "uint16", "name": "reportVersion", "type": "uint16" }
- { "internalType": "bytes", "name": "verifiedReportData", "type": "bytes" }
outputs: []
18 changes: 16 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,27 @@ services:
- DATASTREAMS_CLIENT_SECRET=${DATASTREAMS_CLIENT_SECRET}
volumes:
- ./logs:/transmitter/logs
- ./config.yml:/transmitter/config.yml
depends_on:
- transmitter-redis

transmitter-redis:
image: redis:latest
command: redis-server --save 60 1 --appendonly yes --requirepass ${REDIS_PASSWORD}
image: redis/redis-stack:latest
container_name: transmitter-redis
ports:
- '6379:6379'
volumes:
- ./data:/data

command: >
sh -c "
if [ -n \"$REDIS_PASSWORD\" ]; then
exec redis-stack-server --requirepass \"$REDIS_PASSWORD\" --save 60 1 --appendonly yes
else
exec redis-stack-server --save 60 1 --appendonly yes
fi
"

networks:
default:
driver: bridge
6 changes: 6 additions & 0 deletions server/services/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export async function getAddress() {
return evmAccountAddress;
}

export async function verifyReport(report: StreamReport) {
const vm = await getVm();
if (vm === 'svm') return solanaVerifyReport(report);
return evmVerifyReport(report);
}

export async function getTokenBalance() {
const vm = await getVm();
if (vm === 'svm') return getSolanaBalance();
Expand Down
19 changes: 14 additions & 5 deletions server/services/clientEvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export async function executeContract({
}
}



async function getContractAddresses() {
try {
const clients = await getClients();
Expand Down Expand Up @@ -246,6 +248,12 @@ export async function verifyReport(report: StreamReport) {
verifierProxyAddress,
} = contractAddresses;

// Generate parameter payload for verifyAndUpdateReport calls
const feeTokenAddressEncoded = encodeAbiParameters(
[{ type: 'address', name: 'parameterPayload' }],
[feeTokenAddress]
);

const [fee] = await readContract(publicClient, {
address: feeManagerAddress,
abi: feeManagerAbi,
Expand All @@ -254,11 +262,6 @@ export async function verifyReport(report: StreamReport) {
});
logger.info(`⛽️ Estimated fee: ${formatEther(fee.amount)} LINK`, { fee });

const feeTokenAddressEncoded = encodeAbiParameters(
[{ type: 'address', name: 'parameterPayload' }],
[feeTokenAddress]
);

const approveLinkGas = await estimateContractGas(publicClient, {
account,
address: feeTokenAddress,
Expand Down Expand Up @@ -381,6 +384,8 @@ export async function verifyReport(report: StreamReport) {
verifiedReportData
);
const verifiedReport: ReportV3 = {
reportVersion,
verifiedReport: verifiedReportData as Hex,
feedId,
validFromTimestamp,
observationsTimestamp,
Expand All @@ -391,6 +396,7 @@ export async function verifyReport(report: StreamReport) {
bid,
ask,
rawReport: report.rawReport,
parameterPayload: feeTokenAddressEncoded,
};
logger.info('✅ Report verified', { verifiedReport });
return verifiedReport;
Expand Down Expand Up @@ -419,6 +425,8 @@ export async function verifyReport(report: StreamReport) {
verifiedReportData
);
const verifiedReport: ReportV4 = {
reportVersion,
verifiedReport: verifiedReportData as Hex,
feedId,
validFromTimestamp,
observationsTimestamp,
Expand All @@ -428,6 +436,7 @@ export async function verifyReport(report: StreamReport) {
price,
marketStatus,
rawReport: report.rawReport,
parameterPayload: feeTokenAddressEncoded,
};
logger.info('✅ Report verified', { verifiedReport });
return verifiedReport;
Expand Down
14 changes: 11 additions & 3 deletions server/services/clientSolana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getAllSolanaChains } from '../config/chains';
import { ReportV3, ReportV4, StreamReport } from '../types';
import idl from '../config/idl.json';
import { Verifier } from '../config/idlType';
import { decodeAbiParameters } from 'viem';
import { decodeAbiParameters, Hex } from 'viem';
import { getSolanaVerifier } from '../config/verifiers';
import { base64ToHex, kebabToCamel, printError } from '../utils';
import { BN } from 'bn.js';
Expand Down Expand Up @@ -247,6 +247,8 @@ export async function verifyReport(report: StreamReport) {
`0x${base64ToHex(verifiedReportData)}`
);
const verifiedReport: ReportV3 = {
reportVersion,
verifiedReport: `0x${base64ToHex(verifiedReportData)}` as Hex,
feedId,
validFromTimestamp,
observationsTimestamp,
Expand All @@ -257,6 +259,7 @@ export async function verifyReport(report: StreamReport) {
bid,
ask,
rawReport: report.rawReport,
parameterPayload: undefined, // Solana doesn't use parameter payload
};
return verifiedReport;
}
Expand Down Expand Up @@ -284,6 +287,8 @@ export async function verifyReport(report: StreamReport) {
`0x${base64ToHex(verifiedReportData)}`
);
const verifiedReport: ReportV4 = {
reportVersion,
verifiedReport: `0x${base64ToHex(verifiedReportData)}` as Hex,
feedId,
validFromTimestamp,
observationsTimestamp,
Expand All @@ -293,6 +298,7 @@ export async function verifyReport(report: StreamReport) {
price,
marketStatus,
rawReport: report.rawReport,
parameterPayload: undefined, // Solana doesn't use parameter payload
};
return verifiedReport;
}
Expand Down Expand Up @@ -348,8 +354,10 @@ export async function executeSolanaProgram({
}
const args = instructionArgs.map(({ name, type }) =>
type === 'number'
? new BN(report[name as keyof typeof report].toString())
: report[name as keyof typeof report].toString()
? new BN(
(report[name as keyof typeof report] ?? 0).toString().slice(0, 4)
)
: (report[name as keyof typeof report] ?? '').toString().slice(0, 4)
);
const tx = await method(...args)
.accounts({
Expand Down
7 changes: 7 additions & 0 deletions server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ export type StreamReport = Report & {
bid: bigint;
ask: bigint;
rawReport: Hex;
parameterPayload?: Hex;
};

export type ReportV3 = {
reportVersion: number;
verifiedReport: Hex;
feedId: Hex;
validFromTimestamp: number;
observationsTimestamp: number;
Expand All @@ -23,9 +26,12 @@ export type ReportV3 = {
bid: bigint;
ask: bigint;
rawReport: Hex;
parameterPayload?: Hex;
};

export type ReportV4 = {
reportVersion: number;
verifiedReport: Hex;
feedId: Hex;
validFromTimestamp: number;
observationsTimestamp: number;
Expand All @@ -35,6 +41,7 @@ export type ReportV4 = {
price: bigint;
marketStatus: number;
rawReport: Hex;
parameterPayload?: Hex;
};

export type Feed = { name: string; feedId: string };
Expand Down