diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index ae8d589..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# next-template - -A Next.js 13 template for building apps with Radix UI and Tailwind CSS. - -## Usage - -```bash -npx create-next-app -e https://github.com/shadcn/next-template -``` - -## Features - -- Next.js 13 App Directory -- Radix UI Primitives -- Tailwind CSS -- Icons from [Lucide](https://lucide.dev) -- Dark mode with `next-themes` -- Tailwind CSS class sorting, merging and linting. - -## License - -Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md). diff --git a/frontend/abi/crosspayABI.ts b/frontend/abi/crosspayABI.ts index 0c19e6a..ae97168 100644 --- a/frontend/abi/crosspayABI.ts +++ b/frontend/abi/crosspayABI.ts @@ -1,155 +1,746 @@ -export const crosspayABI = [ - { stateMutability: 'nonpayable', type: 'constructor', inputs: [] }, - { - type: 'event', - anonymous: false, - inputs: [ - { name: 'owner', internalType: 'address', type: 'address', indexed: true }, - { name: 'approved', internalType: 'address', type: 'address', indexed: true }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256', indexed: true }, - ], - name: 'Approval', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { name: 'owner', internalType: 'address', type: 'address', indexed: true }, - { name: 'operator', internalType: 'address', type: 'address', indexed: true }, - { name: 'approved', internalType: 'bool', type: 'bool', indexed: false }, - ], - name: 'ApprovalForAll', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { name: 'from', internalType: 'address', type: 'address', indexed: true }, - { name: 'to', internalType: 'address', type: 'address', indexed: true }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256', indexed: true }, - ], - name: 'Transfer', - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'balanceOf', - outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'getApproved', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'user', internalType: 'address', type: 'address' }], - name: 'getPointer', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [ - { name: 'owner', internalType: 'address', type: 'address' }, - { name: 'operator', internalType: 'address', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - }, - { stateMutability: 'view', type: 'function', inputs: [], name: 'name', outputs: [{ name: '', internalType: 'string', type: 'string' }] }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: '', internalType: 'address', type: 'address' }], - name: 'pointers', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - { name: 'data', internalType: 'bytes', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'operator', internalType: 'address', type: 'address' }, - { name: 'approved', internalType: 'bool', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [{ name: 'pointerValue', internalType: 'string', type: 'string' }], - name: 'setPointer', - outputs: [], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'interfaceId', internalType: 'bytes4', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - }, - { stateMutability: 'view', type: 'function', inputs: [], name: 'symbol', outputs: [{ name: '', internalType: 'string', type: 'string' }] }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - }, - ] as const \ No newline at end of file +export const crosspayABI = [ + { + "inputs": [ + { + "internalType": "address", + "name": "pythContract", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "baseURI", + "type": "string" + } + ], + "name": "BaseURISet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "pointer", + "type": "string" + } + ], + "name": "PointerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "users", + "type": "address[]" + }, + { + "internalType": "string[]", + "name": "pointerValues", + "type": "string[]" + } + ], + "name": "batchSetPointers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getPointer", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "priceUpdate", + "type": "bytes[]" + } + ], + "name": "getprices", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pointers", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newBaseURI", + "type": "string" + } + ], + "name": "setBaseURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "pointerValue", + "type": "string" + } + ], + "name": "setPointer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pythContract", + "type": "address" + } + ], + "name": "setPythContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const \ No newline at end of file diff --git a/frontend/app/api/store/route.ts b/frontend/app/api/store/route.ts index 1f64829..4fae26a 100644 --- a/frontend/app/api/store/route.ts +++ b/frontend/app/api/store/route.ts @@ -1,14 +1,13 @@ import { NextRequest, NextResponse } from "next/server" import { PinataSDK } from "pinata" -import { Web3Storage } from "web3.storage" -export const config = { +const config = { api: { bodyParser: false, }, } -export const pinata = new PinataSDK({ +const pinata = new PinataSDK({ pinataJwt: `${process.env.NEXT_PUBLIC_APP_PINATA_JWT}`, pinataGateway: `${process.env.NEXT_PUBLIC_APP_GATEWAY_URL}`, }) diff --git a/frontend/components/pyth-pricefeed.tsx b/frontend/components/pyth-pricefeed.tsx new file mode 100644 index 0000000..e11b111 --- /dev/null +++ b/frontend/components/pyth-pricefeed.tsx @@ -0,0 +1,91 @@ +// @ts-nocheck +import React, { use, useState } from 'react'; +import { ethers } from 'ethers'; +import { siteConfig } from '@/config/site'; +import { useChainId } from 'wagmi'; +import { crosspayABI } from '@/abi/crosspayABI'; + +const PriceFeeds = () => { + const [selectedPriceIds, setSelectedPriceIds] = useState([]); + const [prices, setPrices] = useState([]); + + const connectedchainid = useChainId(); + + const PYTH_CONTRACT_ADDRESS = siteConfig.crosspayaddress[connectedchainid]; + + const priceFeedIds = [ + { id: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace', symbol: 'ETH/USD' }, + { id: '0x7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f7', symbol: 'BTC/USD' }, + { id: '0x7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f8', symbol: 'USDC/USD' }, + { id: '0x7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f7d5d8c6c7f5c5d5c7b5f9', symbol: 'USDT/USD' }, + ]; + + const fetchPriceFeeds = async () => { + try { + const provider = new ethers.BrowserProvider(window?.ethereum); + const signer = provider.getSigner(); + const contract = new ethers.Contract(PYTH_CONTRACT_ADDRESS, crosspayABI, signer); + + const priceUpdates = selectedPriceIds.map((id) => ethers.hexlify(id)); + const prices = await contract.getPrices(priceUpdates); + + setPrices(prices); + } catch (error) { + console.error('Error fetching price feeds:', error); + } + }; + + const handlePriceIdClick = (priceId) => { + setSelectedPriceIds((prevIds) => { + if (prevIds.includes(priceId)) { + return prevIds.filter((id) => id !== priceId); + } else { + return [...prevIds, priceId]; + } + }); + }; + + return ( +
+

Price Feeds

+ +
+ {priceFeedIds.map((feed) => ( + + ))} +
+ + + + {prices.length > 0 && ( +
+

Price Feeds:

+ +
+ )} +
+ ); +}; + +export default PriceFeeds; \ No newline at end of file diff --git a/smartcontract/contracts/MerchantsPay.sol b/smartcontract/contracts/MerchantsPay.sol index ea162d9..40e3c13 100644 --- a/smartcontract/contracts/MerchantsPay.sol +++ b/smartcontract/contracts/MerchantsPay.sol @@ -7,12 +7,10 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; - import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; contract MerchantsPay is ERC721, Ownable, AccessControl, ReentrancyGuard { - IPyth pyth; bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); @@ -27,9 +25,6 @@ contract MerchantsPay is ERC721, Ownable, AccessControl, ReentrancyGuard { constructor(address pythContract) ERC721("CrossChain MerchantsPay", "CMP") { _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); _setupRole(ADMIN_ROLE, msg.sender); - - // The IPyth interface from pyth-sdk-solidity provides the methods to interact with the Pyth contract. - // Instantiate it with the Pyth contract address from https://docs.pyth.network/price-feeds/contract-addresses/evm pyth = IPyth(pythContract); } @@ -47,7 +42,7 @@ contract MerchantsPay is ERC721, Ownable, AccessControl, ReentrancyGuard { _safeMint(msg.sender, tokenId); emit PointerSet(msg.sender, pointerValue); } - + function getPointer(address user) public view returns (string memory) { return pointers[user]; } @@ -88,17 +83,11 @@ contract MerchantsPay is ERC721, Ownable, AccessControl, ReentrancyGuard { return super.supportsInterface(interfaceId); } - function getprices(bytes[] calldata priceUpdate) public payable { - // Submit a priceUpdate to the Pyth contract to update the on-chain price. - // Updating the price requires paying the fee returned by getUpdateFee. - // WARNING: These lines are required to ensure the getPriceNoOlderThan call below succeeds. If you remove them, transactions may fail with "0x19abf40e" error. - uint fee = pyth.getUpdateFee(priceUpdate); - pyth.updatePriceFeeds{ value: fee }(priceUpdate); - - // Read the current price from a price feed if it is less than 60 seconds old. - // Each price feed (e.g., ETH/USD) is identified by a price feed ID. - // The complete list of feed IDs is available at https://pyth.network/developers/price-feed-ids - bytes32 priceFeedId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; // ETH/USD - PythStructs.Price memory price = pyth.getPriceNoOlderThan(priceFeedId, 60); + function getPrices(bytes32[] calldata priceFeedIds) public view returns (PythStructs.Price[] memory) { + PythStructs.Price[] memory prices = new PythStructs.Price[](priceFeedIds.length); + for (uint256 i = 0; i < priceFeedIds.length; i++) { + prices[i] = pyth.getPriceNoOlderThan(priceFeedIds[i], 60); + } + return prices; } -} +} \ No newline at end of file