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";
contract TokenTransferor is OwnerIsCreator {
using SafeERC20 for IERC20;
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance);
error NothingToWithdraw();
error FailedToWithdrawEth(address owner, address target, uint256 value);
error DestinationChainNotAllowlisted(uint64 destinationChainSelector);
error InvalidReceiverAddress();
event TokensTransferred(
bytes32 indexed messageId,
uint64 indexed destinationChainSelector,
address receiver,
address token,
uint256 tokenAmount,
address feeToken,
uint256 fees
);
mapping(uint64 => bool) public allowlistedChains;
IRouterClient private s_router;
IERC20 private s_linkToken;
constructor(
address _router,
address _link
) {
s_router = IRouterClient(_router);
s_linkToken = IERC20(_link);
}
modifier onlyAllowlistedChain(
uint64 _destinationChainSelector
) {
if (!allowlistedChains[_destinationChainSelector]) {
revert DestinationChainNotAllowlisted(_destinationChainSelector);
}
_;
}
modifier validateReceiver(
address _receiver
) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedChains[_destinationChainSelector] = allowed;
}
function transferTokensPayLINK(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _token, _amount, address(s_linkToken));
uint256 fees = s_router.getFee(_destinationChainSelector, evm2AnyMessage);
uint256 requiredLinkBalance;
if (_token == address(s_linkToken)) {
requiredLinkBalance = fees + _amount;
} else {
requiredLinkBalance = fees;
}
uint256 linkBalance = s_linkToken.balanceOf(address(this));
if (requiredLinkBalance > linkBalance) {
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
}
s_linkToken.approve(address(s_router), requiredLinkBalance);
if (_token != address(s_linkToken)) {
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (_amount > tokenBalance) {
revert NotEnoughBalance(tokenBalance, _amount);
}
IERC20(_token).approve(address(s_router), _amount);
}
messageId = s_router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit TokensTransferred(messageId, _destinationChainSelector, _receiver, _token, _amount, address(s_linkToken), fees);
return messageId;
}
function transferTokensPayNative(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _token, _amount, address(0));
uint256 fees = s_router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > address(this).balance) {
revert NotEnoughBalance(address(this).balance, fees);
}
IERC20(_token).approve(address(s_router), _amount);
messageId = s_router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage);
emit TokensTransferred(messageId, _destinationChainSelector, _receiver, _token, _amount, address(0), fees);
return messageId;
}
function _buildCCIPMessage(
address _receiver,
address _token,
uint256 _amount,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount});
return Client.EVM2AnyMessage({
receiver: abi.encode(_receiver),
data: "",
tokenAmounts: tokenAmounts,
extraArgs: Client._argsToBytes(
Client.GenericExtraArgsV2({
gasLimit: 0,
allowOutOfOrderExecution: true
})
),
feeToken: _feeTokenAddress
});
}
receive() external payable {}
function withdraw(
address _beneficiary
) public onlyOwner {
uint256 amount = address(this).balance;
if (amount == 0) revert NothingToWithdraw();
(bool sent,) = _beneficiary.call{value: amount}("");
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
uint256 amount = IERC20(_token).balanceOf(address(this));
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract SVRConsumer {
AggregatorV3Interface internal svrFeed;
constructor(
address _svrFeedAddress
) {
svrFeed = AggregatorV3Interface(_svrFeedAddress);
}
function getLatestPrice() public view returns (int256) {
(,
int256 price,
,,
) = 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",
logging: {
logger: console,
logLevel: LogLevel.INFO,
},
}
const client = createClient(config)
console.log(`\nFetching latest report for feed ${feedId} (${version})...\n`)
const report = await client.getLatestReport(feedId)
console.log(`Raw Report Blob: ${report.fullReport}`)
const decodedData = decodeReport(report.fullReport, report.feedID)
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()