diff --git a/api/contract/abi.ts b/api/contract/abi.ts index 20a613a..c0aeb28 100644 --- a/api/contract/abi.ts +++ b/api/contract/abi.ts @@ -1,6 +1,770 @@ import { AbiItem } from 'caver-js' -const abi: AbiItem[] = [ +export const ftAbi: AbiItem[] = [ + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'subtractedValue', + type: 'uint256', + }, + ], + name: 'decreaseAllowance', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'player', + type: 'address', + }, + ], + name: 'ensureAttackAmount', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'addedValue', + type: 'uint256', + }, + ], + name: 'increaseAllowance', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'safeTransfer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransfer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + 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: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export const nftAbi: AbiItem[] = [ + { + inputs: [], + 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: 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: [ + { + 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: [ + { + internalType: 'address', + name: 'player', + type: 'address', + }, + { + internalType: 'string', + name: 'tokenURI', + type: 'string', + }, + ], + name: 'ensureAttackAmount', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + 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: '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: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + 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: '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', + }, +] + +export const sbtAbi: AbiItem[] = [ { inputs: [], stateMutability: 'nonpayable', @@ -127,7 +891,7 @@ const abi: AbiItem[] = [ }, { internalType: 'string', - name: 'tokenURI', + name: '_tokenURI', type: 'string', }, ], @@ -360,6 +1124,4 @@ const abi: AbiItem[] = [ stateMutability: 'nonpayable', type: 'function', }, -] - -export default abi +] \ No newline at end of file diff --git a/api/contract/controllers.ts b/api/contract/controllers.ts index bd008a7..2a6d7d3 100644 --- a/api/contract/controllers.ts +++ b/api/contract/controllers.ts @@ -1,7 +1,8 @@ +import axios from 'axios' import Caver from 'caver-js' import type { NextApiRequest, NextApiResponse } from 'next' import { Blob } from 'nft.storage' -import abi from './abi' +import { ftAbi, nftAbi, sbtAbi } from './abi' import * as contractService from './services' export type PostPayload = { @@ -45,22 +46,68 @@ export async function ensureAttackAmount( ) caver.wallet.add(key) - const contract = new caver.contract(abi, process.env.CONTRACT_ADDRESS) + // estimate gasPrice + const { data: gasPrice } = await axios.get<{ + result: string + }>('https://api.baobab.klaytn.net:8651', { + headers: { + 'Content-Type': 'application/json', + }, + data: { + jsonrpc: '2.0', + method: 'klay_gasPrice', + params: [], + id: 1, + }, + }) - const tx = await contract.methods - .ensureAttackAmount( - address, - // key.address, - uri, - ) + const effectiveGasPrice = caver.utils.hexToNumber(gasPrice.result) + + const ftContract = new caver.contract( + ftAbi, + process.env.KUPFT_CONTRACT_ADDRESS, + ) + + const ftTx = await ftContract.methods.ensureAttackAmount(address).send({ + from: key.address, + // gasPrice: '75000000000', + gasPrice: effectiveGasPrice.toString(), + gas: 1000000000, + }) + + const nftContract = new caver.contract( + nftAbi, + process.env.KUPNFT_CONTRACT_ADDRESS, + ) + + const nftTx = await nftContract.methods + .ensureAttackAmount(address, uri) + .send({ + from: key.address, + // gasPrice: '75000000000', + gasPrice: effectiveGasPrice.toString(), + gas: 1000000, + }) + + const sbtContract = new caver.contract( + sbtAbi, + process.env.KUPSBT_CONTRACT_ADDRESS, + ) + + const sbtTx = await sbtContract.methods + .ensureAttackAmount(address, uri) .send({ - // from: address, from: key.address, - gasPrice: '75000000000', + // gasPrice: '75000000000', + gasPrice: effectiveGasPrice.toString(), gas: 1000000, }) - res.status(200).json(tx) + res.status(200).json({ + ft: ftTx.transactionHash, + nft: nftTx.transactionHash, + sbt: sbtTx.transactionHash, + }) } catch (error: any) { console.log(error) res.status(500).json({ error: error.message }) diff --git a/features/Contract/components/ContractIntract.tsx b/features/Contract/components/ContractIntract.tsx index db0f933..00e4cf3 100644 --- a/features/Contract/components/ContractIntract.tsx +++ b/features/Contract/components/ContractIntract.tsx @@ -37,12 +37,7 @@ const ContractInteract: React.FC<{ status === 200 && openModal({ title: 'Contract', - component: ( - - ), + component: , }) setLoading(false) }) @@ -80,4 +75,4 @@ const ContractInteract: React.FC<{ ) } -export default ContractInteract +export default ContractInteract \ No newline at end of file diff --git a/features/Contract/components/ContractSuccess.tsx b/features/Contract/components/ContractSuccess.tsx index 15683c2..55977e3 100644 --- a/features/Contract/components/ContractSuccess.tsx +++ b/features/Contract/components/ContractSuccess.tsx @@ -3,9 +3,13 @@ import axios from 'axios' import { useEffect, useState } from 'react' const ContractSuccess: React.FC<{ - txHash: string + txs: { + ft: string + nft: string + sbt: string + } ipnft: string -}> = ({ txHash, ipnft }) => { +}> = ({ txs, ipnft }) => { const [nftImageUrl, setNftImageUrl] = useState(null) useEffect(() => { axios @@ -36,16 +40,35 @@ const ContractSuccess: React.FC<{ {nftImageUrl && nft} - - Transaction Info - +
+ + FT Transaction Info + + + NFT Transaction Info + + + + SBT Transaction Info + +
) } -export default ContractSuccess +export default ContractSuccess \ No newline at end of file diff --git a/types/env.d.ts b/types/env.d.ts index b33486a..8c32547 100644 --- a/types/env.d.ts +++ b/types/env.d.ts @@ -2,9 +2,12 @@ declare namespace NodeJS { interface ProcessEnv { NEXT_PUBLIC_PLAYFAB_TITLEID: string NFT_STORAGE_API_KEY: string - CONTRACT_ADDRESS: string RPC_URL: string API_KEY: string KEY_RING: string + + KUPFT_CONTRACT_ADDRESS: string + KUPNFT_CONTRACT_ADDRESS: string + KUPSBT_CONTRACT_ADDRESS: string } } \ No newline at end of file diff --git a/utils/requester.ts b/utils/requester.ts index 2ca394e..76addfc 100644 --- a/utils/requester.ts +++ b/utils/requester.ts @@ -3,6 +3,7 @@ import type { AxiosError, AxiosRequestConfig } from 'axios' import { toast } from 'react-toastify' const titleId: string = process.env.NEXT_PUBLIC_PLAYFAB_TITLEID + const Requester = axios.create({ baseURL: `https://${titleId}.playfabapi.com`, }) @@ -27,11 +28,18 @@ Requester.interceptors.response.use( return response }, (error) => { - // console.log('📲❌', error) - if (axios.isAxiosError(error)) { const axiosError = error as AxiosError - return Promise.reject(axiosError.response?.data), toast(axiosError.response?.data.errorMessage); + + // when the sessionTicket's time is expired, remove it from localStorage + if (axiosError.response?.data.error === 'InvalidTicket') { + localStorage.removeItem('SessionTicket') + } + + return ( + Promise.reject(axiosError.response?.data), + toast(axiosError.response?.data.errorMessage) + ) } return Promise.reject(error)