// Import the ethers library from npmconst{ethers}=awaitimport("npm:ethers@6.10.0");constHash=awaitimport("npm:ipfs-only-hash@4.0.0");// Make an HTTP request to fetch real estate dataconstapiResponse=awaitFunctions.makeHttpRequest({url:`https://api.bridgedataoutput.com/api/v2/OData/test/Property('P_5dba1fb94aa4055b9f29696f')?access_token=6baca547742c6f96a6ff71b138424f21`,});// Extract relevant data from the API responseconstrealEstateAddress=apiResponse.data.UnparsedAddress;constyearBuilt=Number(apiResponse.data.YearBuilt);constlotSizeSquareFeet=Number(apiResponse.data.LotSizeSquareFeet);constlivingArea=Number(apiResponse.data.LivingArea);constbedroomsTotal=Number(apiResponse.data.BedroomsTotal);constmetadata={name:"Real Estate Token",attributes: [{trait_type:"realEstateAddress",value:realEstateAddress},{trait_type:"yearBuilt",value:yearBuilt},{trait_type:"lotSizeSquareFeet",value:lotSizeSquareFeet},{trait_type:"livingArea",value:livingArea},{trait_type:"bedroomsTotal",value:bedroomsTotal} ]};// Stringify the JSON objectconstmetadataString=JSON.stringify(metadata);constipfsCid=awaitHash.of(metadataString);console.log(ipfsCid);returnFunctions.encodeString(`ipfs://${ipfsCid}`);
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ERC1155Supply, ERC1155} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.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.
*/
contract ERC1155Core is ERC1155Supply, OwnerIsCreator {
address internal s_issuer;
// Optional mapping for token URIs
mapping(uint256 tokenId => string) private _tokenURIs;
event SetIssuer(address indexed issuer);
error ERC1155Core_CallerIsNotIssuerOrItself(address msgSender);
modifier onlyIssuerOrItself() {
if (msg.sender != address(this) && msg.sender != s_issuer) {
revert ERC1155Core_CallerIsNotIssuerOrItself(msg.sender);
}
_;
}
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
constructor(string memory uri_) ERC1155(uri_) {}
function setIssuer(address _issuer) external onlyOwner {
s_issuer = _issuer;
emit SetIssuer(_issuer);
}
function mint(address _to, uint256 _id, uint256 _amount, bytes memory _data, string memory _tokenUri)
public
onlyIssuerOrItself
{
_mint(_to, _id, _amount, _data);
_tokenURIs[_id] = _tokenUri;
}
function mintBatch(
address _to,
uint256[] memory _ids,
uint256[] memory _amounts,
bytes memory _data,
string[] memory _tokenUris
) public onlyIssuerOrItself {
_mintBatch(_to, _ids, _amounts, _data);
for (uint256 i = 0; i < _ids.length; ++i) {
_tokenURIs[_ids[i]] = _tokenUris[i];
}
}
function burn(address account, uint256 id, uint256 amount) public onlyIssuerOrItself {
if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) {
revert ERC1155MissingApprovalForAll(_msgSender(), account);
}
_burn(account, id, amount);
}
function burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) public onlyIssuerOrItself {
if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) {
revert ERC1155MissingApprovalForAll(_msgSender(), account);
}
_burnBatch(account, ids, amounts);
}
function uri(uint256 tokenId) public view override returns (string memory) {
string memory tokenURI = _tokenURIs[tokenId];
return bytes(tokenURI).length > 0 ? tokenURI : super.uri(tokenId);
}
function _setURI(uint256 tokenId, string memory tokenURI) internal {
_tokenURIs[tokenId] = tokenURI;
emit URI(uri(tokenId), tokenId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ERC1155Core, ERC1155} from "./ERC1155Core.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Withdraw} from "./utils/Withdraw.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.
*/
contract CrossChainBurnAndMintERC1155 is ERC1155Core, IAny2EVMMessageReceiver, ReentrancyGuard, Withdraw {
enum PayFeesIn {
Native,
LINK
}
error InvalidRouter(address router);
error NotEnoughBalanceForFees(uint256 currentBalance, uint256 calculatedFees);
error ChainNotEnabled(uint64 chainSelector);
error SenderNotEnabled(address sender);
error OperationNotAllowedOnCurrentChain(uint64 chainSelector);
struct XNftDetails {
address xNftAddress;
bytes ccipExtraArgsBytes;
}
IRouterClient internal immutable i_ccipRouter;
LinkTokenInterface internal immutable i_linkToken;
uint64 private immutable i_currentChainSelector;
mapping(uint64 destChainSelector => XNftDetails xNftDetailsPerChain) public s_chains;
event ChainEnabled(uint64 chainSelector, address xNftAddress, bytes ccipExtraArgs);
event ChainDisabled(uint64 chainSelector);
event CrossChainSent(
address from,
address to,
uint256 id,
uint256 amount,
bytes data,
uint64 sourceChainSelector,
uint64 destinationChainSelector
);
event CrossChainReceived(
address from,
address to,
uint256 id,
uint256 amount,
bytes data,
uint64 sourceChainSelector,
uint64 destinationChainSelector
);
modifier onlyRouter() {
if (msg.sender != address(i_ccipRouter)) {
revert InvalidRouter(msg.sender);
}
_;
}
modifier onlyEnabledChain(uint64 _chainSelector) {
if (s_chains[_chainSelector].xNftAddress == address(0)) {
revert ChainNotEnabled(_chainSelector);
}
_;
}
modifier onlyEnabledSender(uint64 _chainSelector, address _sender) {
if (s_chains[_chainSelector].xNftAddress != _sender) {
revert SenderNotEnabled(_sender);
}
_;
}
modifier onlyOtherChains(uint64 _chainSelector) {
if (_chainSelector == i_currentChainSelector) {
revert OperationNotAllowedOnCurrentChain(_chainSelector);
}
_;
}
constructor(string memory uri_, address ccipRouterAddress, address linkTokenAddress, uint64 currentChainSelector)
ERC1155Core(uri_)
{
i_ccipRouter = IRouterClient(ccipRouterAddress);
i_linkToken = LinkTokenInterface(linkTokenAddress);
i_currentChainSelector = currentChainSelector;
}
function enableChain(uint64 chainSelector, address xNftAddress, bytes memory ccipExtraArgs)
external
onlyOwner
onlyOtherChains(chainSelector)
{
s_chains[chainSelector] = XNftDetails({xNftAddress: xNftAddress, ccipExtraArgsBytes: ccipExtraArgs});
emit ChainEnabled(chainSelector, xNftAddress, ccipExtraArgs);
}
function disableChain(uint64 chainSelector) external onlyOwner onlyOtherChains(chainSelector) {
delete s_chains[chainSelector];
emit ChainDisabled(chainSelector);
}
function crossChainTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data,
uint64 destinationChainSelector,
PayFeesIn payFeesIn
) external nonReentrant onlyEnabledChain(destinationChainSelector) returns (bytes32 messageId) {
string memory tokenUri = uri(id);
burn(from, id, amount);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(s_chains[destinationChainSelector].xNftAddress),
data: abi.encode(from, to, id, amount, data, tokenUri),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: s_chains[destinationChainSelector].ccipExtraArgsBytes,
feeToken: payFeesIn == PayFeesIn.LINK ? address(i_linkToken) : address(0)
});
// Get the fee required to send the CCIP message
uint256 fees = i_ccipRouter.getFee(destinationChainSelector, message);
if (payFeesIn == PayFeesIn.LINK) {
if (fees > i_linkToken.balanceOf(address(this))) {
revert NotEnoughBalanceForFees(i_linkToken.balanceOf(address(this)), fees);
}
// Approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
i_linkToken.approve(address(i_ccipRouter), fees);
// Send the message through the router and store the returned message ID
messageId = i_ccipRouter.ccipSend(destinationChainSelector, message);
} else {
if (fees > address(this).balance) {
revert NotEnoughBalanceForFees(address(this).balance, fees);
}
// Send the message through the router and store the returned message ID
messageId = i_ccipRouter.ccipSend{value: fees}(destinationChainSelector, message);
}
emit CrossChainSent(from, to, id, amount, data, i_currentChainSelector, destinationChainSelector);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC1155) returns (bool) {
return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || super.supportsInterface(interfaceId);
}
/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(Client.Any2EVMMessage calldata message)
external
virtual
override
onlyRouter
nonReentrant
onlyEnabledChain(message.sourceChainSelector)
onlyEnabledSender(message.sourceChainSelector, abi.decode(message.sender, (address)))
{
uint64 sourceChainSelector = message.sourceChainSelector;
(address from, address to, uint256 id, uint256 amount, bytes memory data, string memory tokenUri) =
abi.decode(message.data, (address, address, uint256, uint256, bytes, string));
mint(to, id, amount, data, tokenUri);
emit CrossChainReceived(from, to, id, amount, data, sourceChainSelector, i_currentChainSelector);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.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.
*/
contract Withdraw is OwnerIsCreator {
using SafeERC20 for IERC20;
error NothingToWithdraw();
error FailedToWithdrawEth(address owner, address target, uint256 value);
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);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
import {FunctionsSource} from "./FunctionsSource.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.
*/
contract RealEstatePriceDetails is FunctionsClient, FunctionsSource, OwnerIsCreator {
using FunctionsRequest for FunctionsRequest.Request;
struct PriceDetails {
uint80 listPrice;
uint80 originalListPrice;
uint80 taxAssessedValue;
}
address internal s_automationForwarderAddress;
mapping(uint256 tokenId => PriceDetails) internal s_priceDetails;
error OnlyAutomationForwarderOrOwnerCanCall();
modifier onlyAutomationForwarderOrOwner() {
if (msg.sender != s_automationForwarderAddress && msg.sender != owner()) {
revert OnlyAutomationForwarderOrOwnerCanCall();
}
_;
}
constructor(address functionsRouterAddress) FunctionsClient(functionsRouterAddress) {}
function setAutomationForwarder(address automationForwarderAddress) external onlyOwner {
s_automationForwarderAddress = automationForwarderAddress;
}
function updatePriceDetails(string memory tokenId, uint64 subscriptionId, uint32 gasLimit, bytes32 donID)
external
onlyAutomationForwarderOrOwner
returns (bytes32 requestId)
{
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(this.getPrices());
string[] memory args = new string[](1);
args[0] = tokenId;
req.setArgs(args);
requestId = _sendRequest(req.encodeCBOR(), subscriptionId, gasLimit, donID);
}
function getPriceDetails(uint256 tokenId) external view returns (PriceDetails memory) {
return s_priceDetails[tokenId];
}
function fulfillRequest(bytes32, /*requestId*/ bytes memory response, bytes memory err) internal override {
if (err.length != 0) {
revert(string(err));
}
(uint256 tokenId, uint256 listPrice, uint256 originalListPrice, uint256 taxAssessedValue) =
abi.decode(response, (uint256, uint256, uint256, uint256));
s_priceDetails[tokenId] = PriceDetails({
listPrice: uint80(listPrice),
originalListPrice: uint80(originalListPrice),
taxAssessedValue: uint80(taxAssessedValue)
});
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {CrossChainBurnAndMintERC1155} from "./CrossChainBurnAndMintERC1155.sol";
import {RealEstatePriceDetails} from "./RealEstatePriceDetails.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.
*/
contract RealEstateToken is CrossChainBurnAndMintERC1155, RealEstatePriceDetails {
/**
*
* ██████╗ ███████╗ █████╗ ██╗ ███████╗███████╗████████╗ █████╗ ████████╗███████╗ ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗
* ██╔══██╗██╔════╝██╔══██╗██║ ██╔════╝██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔════╝ ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║
* ██████╔╝█████╗ ███████║██║ █████╗ ███████╗ ██║ ███████║ ██║ █████╗ ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║
* ██╔══██╗██╔══╝ ██╔══██║██║ ██╔══╝ ╚════██║ ██║ ██╔══██║ ██║ ██╔══╝ ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║
* ██║ ██║███████╗██║ ██║███████╗ ███████╗███████║ ██║ ██║ ██║ ██║ ███████╗ ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║
* ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝
*
*/
constructor(
string memory uri_,
address ccipRouterAddress,
address linkTokenAddress,
uint64 currentChainSelector,
address functionsRouterAddress
)
CrossChainBurnAndMintERC1155(uri_, ccipRouterAddress, linkTokenAddress, currentChainSelector)
RealEstatePriceDetails(functionsRouterAddress)
{}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {RealEstateToken} from "./RealEstateToken.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
import {FunctionsSource} from "./FunctionsSource.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.
*/
contract Issuer is FunctionsClient, FunctionsSource, OwnerIsCreator {
using FunctionsRequest for FunctionsRequest.Request;
error LatestIssueInProgress();
struct FractionalizedNft {
address to;
uint256 amount;
}
RealEstateToken internal immutable i_realEstateToken;
bytes32 internal s_lastRequestId;
uint256 private s_nextTokenId;
mapping(bytes32 requestId => FractionalizedNft) internal s_issuesInProgress;
constructor(address realEstateToken, address functionsRouterAddress) FunctionsClient(functionsRouterAddress) {
i_realEstateToken = RealEstateToken(realEstateToken);
}
function issue(address to, uint256 amount, uint64 subscriptionId, uint32 gasLimit, bytes32 donID)
external
onlyOwner
returns (bytes32 requestId)
{
if (s_lastRequestId != bytes32(0)) revert LatestIssueInProgress();
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(this.getNftMetadata());
requestId = _sendRequest(req.encodeCBOR(), subscriptionId, gasLimit, donID);
s_issuesInProgress[requestId] = FractionalizedNft(to, amount);
s_lastRequestId = requestId;
}
function cancelPendingRequest() external onlyOwner {
s_lastRequestId = bytes32(0);
}
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
if (err.length != 0) {
revert(string(err));
}
if (s_lastRequestId == requestId) {
string memory tokenURI = string(response);
uint256 tokenId = s_nextTokenId++;
FractionalizedNft memory fractionalizedNft = s_issuesInProgress[requestId];
i_realEstateToken.mint(fractionalizedNft.to, tokenId, fractionalizedNft.amount, "", tokenURI);
s_lastRequestId = bytes32(0);
}
}
}