Build on the Chainlink Platform

Start your Chainlink journey
Learn & Explore
Build & Integrate
Operate & Scale
Explore Cross-Chain Interoperability with CCIP
Learn cross-chain concepts, workflows, and real-world use cases.
Build Cross-Chain Apps with CCIP Tutorials
Follow step-by-step guides with language switching (EVM, Rust, Move, etc.).
Monitor CCIP Transactions in Real Time
Track the progress and status of cross-chain transactions.
Understand How Data Feeds Power dApps
See how oracle data feeds deliver price feeds and reference data.
Integrate Data Feeds into Smart Contracts
Plug feeds into your apps with examples and addresses.
Select Quality Data Feeds
Evaluate and maintain data feed quality for accurate, reliable integrations.
Learn How Data Streams Deliver Real-Time Data
Understand how low-latency streams support time-sensitive applications.
Implement Real-Time Use Cases with Data Streams
Use low-latency data in trading, gaming, and other live applications.
Deliver Reliable Low-Latency Data with Streams
Operate Data Streams at scale for critical, time-sensitive use cases.
Try it out
import { cre } from "@chainlink/cre-sdk"
cre.handler(
cron.trigger({ schedule: "0 */5 * * *" }), // Every 5 minutes
(runtime) => {
// Fetch data from API
const price = httpClient.get(url).result()
// Read from EVM blockchain
const threshold = evmClient.read(contract).result()
// Make any computation
const shouldUpdate = price > threshold
// Write result onchain
return evmClient.write(contract, price).result()
}
)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
import {OwnerIsCreator} from "@chainlink/contracts@1.4.0/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple contract for transferring tokens across chains.
contract TokenTransferor is OwnerIsCreator {
using SafeERC20 for IERC20;
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance); // Used to make sure contract has enough
// token balance
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been
// allowlisted by the contract owner.
error InvalidReceiverAddress(); // Used when the receiver address is 0.
// Event emitted when the tokens are transferred to an account on another chain.
// The chain selector of the destination chain.
// The address of the receiver on the destination chain.
// The token address that was transferred.
// The token amount that was transferred.
// the token address used to pay CCIP fees.
// The fees paid for sending the message.
event TokensTransferred( // The unique ID of the message.
bytes32 indexed messageId,
uint64 indexed destinationChainSelector,
address receiver,
address token,
uint256 tokenAmount,
address feeToken,
uint256 fees
);
// Mapping to keep track of allowlisted destination chains.
mapping(uint64 => bool) public allowlistedChains;
IRouterClient private s_router;
IERC20 private s_linkToken;
/// @notice Constructor initializes the contract with the router address.
/// @param _router The address of the router contract.
/// @param _link The address of the link contract.
constructor(
address _router,
address _link
) {
s_router = IRouterClient(_router);
s_linkToken = IERC20(_link);
}
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
modifier onlyAllowlistedChain(
uint64 _destinationChainSelector
) {
if (!allowlistedChains[_destinationChainSelector]) {
revert DestinationChainNotAllowlisted(_destinationChainSelector);
}
_;
}
/// @dev Modifier that checks the receiver address is not 0.
/// @param _receiver The receiver address.
modifier validateReceiver(
address _receiver
) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
/// @dev Updates the allowlist status of a destination chain for transactions.
/// @notice This function can only be called by the owner.
/// @param _destinationChainSelector The selector of the destination chain to be updated.
/// @param allowed The allowlist status to be set for the destination chain.
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedChains[_destinationChainSelector] = allowed;
}
/// @notice Transfer tokens to receiver on the destination chain.
/// @notice pay in LINK.
/// @notice the token must be in the list of supported tokens.
/// @notice This function can only be called by the owner.
/// @dev Assumes your contract has sufficient LINK tokens to pay for the fees.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the message that was sent.
function transferTokensPayLINK(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(linkToken) means fees are paid in LINK
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _token, _amount, address(s_linkToken));
// Get the fee required to send the message
uint256 fees = s_router.getFee(_destinationChainSelector, evm2AnyMessage);
uint256 requiredLinkBalance;
if (_token == address(s_linkToken)) {
// Required LINK Balance is the sum of fees and amount to transfer, if the token to transfer is LINK
requiredLinkBalance = fees + _amount;
} else {
requiredLinkBalance = fees;
}
uint256 linkBalance = s_linkToken.balanceOf(address(this));
if (requiredLinkBalance > linkBalance) {
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
}
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the requiredLinkBalance
s_linkToken.approve(address(s_router), requiredLinkBalance);
// If sending a token other than LINK, approve it separately
if (_token != address(s_linkToken)) {
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (_amount > tokenBalance) {
revert NotEnoughBalance(tokenBalance, _amount);
}
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(s_router), _amount);
}
// Send the message through the router and store the returned message ID
messageId = s_router.ccipSend(_destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit TokensTransferred(messageId, _destinationChainSelector, _receiver, _token, _amount, address(s_linkToken), fees);
// Return the message ID
return messageId;
}
/// @notice Transfer tokens to receiver on the destination chain.
/// @notice Pay in native gas such as ETH on Ethereum or POL on Polygon.
/// @notice the token must be in the list of supported tokens.
/// @notice This function can only be called by the owner.
/// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the message that was sent.
function transferTokensPayNative(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(0) means fees are paid in native gas
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _token, _amount, address(0));
// Get the fee required to send the message
uint256 fees = s_router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > address(this).balance) {
revert NotEnoughBalance(address(this).balance, fees);
}
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(s_router), _amount);
// Send the message through the router and store the returned message ID
messageId = s_router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit TokensTransferred(messageId, _destinationChainSelector, _receiver, _token, _amount, address(0), fees);
// Return the message ID
return messageId;
}
/// @notice Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer.
/// @param _receiver The address of the receiver.
/// @param _token The token to be transferred.
/// @param _amount The amount of the token to be transferred.
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP
/// message.
function _buildCCIPMessage(
address _receiver,
address _token,
uint256 _amount,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
// Set the token amounts
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount});
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: "", // No data
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and allowing out-of-order execution.
// Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach
// where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages,
// and ensures compatibility with future CCIP upgrades. Read more about it here:
// https://docs.chain.link/ccip/concepts/best-practices/evm#using-extraargs
Client.GenericExtraArgsV2({
gasLimit: 0, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages
// from
// the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
}
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is transferred to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
/// It should only be callable by the owner of the contract.
/// @param _beneficiary The address to which the Ether should be transferred.
function withdraw(
address _beneficiary
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent,) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
/// @param _beneficiary The address to which the tokens will be sent.
/// @param _token The contract address of the ERC20 token to be withdrawn.
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = IERC20(_token).balanceOf(address(this));
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract SVRConsumer {
AggregatorV3Interface internal svrFeed;
constructor(
address _svrFeedAddress
) {
svrFeed = AggregatorV3Interface(_svrFeedAddress);
}
function getLatestPrice() public view returns (int256) {
(, /* uint80 roundID */
int256 price, /* uint256 startedAt */
/* uint256 timeStamp */
/* uint80 answeredInRound */,,
) = svrFeed.latestRoundData();
return price;
}
}
import { createClient, decodeReport, LogLevel, getReportVersion, formatReport } from "@chainlink/data-streams-sdk"
import "dotenv/config"
async function main() {
if (process.argv.length < 3) {
console.error("Please provide a feed ID as an argument")
console.error("Example: npx tsx singleStream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782")
process.exit(1)
}
const feedId = process.argv[2]
const version = getReportVersion(feedId)
try {
const config = {
apiKey: process.env.API_KEY || "YOUR_API_KEY",
userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET",
endpoint: "https://api.testnet-dataengine.chain.link",
wsEndpoint: "wss://ws.testnet-dataengine.chain.link",
// Comment to disable SDK logging:
logging: {
logger: console,
logLevel: LogLevel.INFO,
},
}
const client = createClient(config)
console.log(`\nFetching latest report for feed ${feedId} (${version})...\n`)
// Get raw report data
const report = await client.getLatestReport(feedId)
console.log(`Raw Report Blob: ${report.fullReport}`)
// Decode the report
const decodedData = decodeReport(report.fullReport, report.feedID)
// Combine decoded data with report metadata
const decodedReport = {
...decodedData,
feedID: report.feedID,
validFromTimestamp: report.validFromTimestamp,
observationsTimestamp: report.observationsTimestamp,
}
console.log(formatReport(decodedReport, version))
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message)
} else {
console.error("Unknown error:", error)
}
process.exit(1)
}
}
main()
Community Events