Skip to content

Commit

Permalink
feat: working sponsored meme creation
Browse files Browse the repository at this point in the history
  • Loading branch information
scguaquetam committed Aug 30, 2024
1 parent 078db8e commit 61abadb
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 53 deletions.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ NEXT_PUBLIC_EXPLORER_TX_BASE_URL=https://explorer.testnet.rootstock.io/tx/
NEXT_PUBLIC_EXPLORER_ADDRESS_BASE_URL=https://explorer.testnet.rootstock.io/address/
NEXT_PUBLIC_PINATA_API_KEY=
NEXT_PUBLIC_PINATA_SECRET_KEY=
NEXT_PUBLIC_PINATA_URL=https://emerald-familiar-cobra-431.mypinata.cloud/ipfs/
NEXT_PUBLIC_PINATA_URL=https://emerald-familiar-cobra-431.mypinata.cloud/ipfs/
NEXT_PUBLIC_BUNDLER_API_KEY="eyJvcmciOiI2NTIzZjY5MzUwOTBmNzAwMDFiYjJkZWIiLCJpZCI6IjMxMDZiOGY2NTRhZTRhZTM4MGVjYjJiN2Q2NDMzMjM4IiwiaCI6Im11cm11cjEyOCJ9"
NEXT_PUBLIC_CUSTOM_BUNDLER_URL="https://rootstocktestnet-bundler.etherspot.io/"
NEXT_PUBLIC_CHAIN_ID="31"
NEXT_PUBLIC_ARKA_PUBLIC_KEY="arka_public_key"
25 changes: 23 additions & 2 deletions components/deployToken/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type FormData = {

const DeployToken: React.FC = () => {
const { isLoggedIn } = useAuth();

const [gasless, setGasless] = useState(true);
const [formData, setFormData] = useState<FormData>({
name: "",
symbol: "",
Expand Down Expand Up @@ -234,10 +234,31 @@ const DeployToken: React.FC = () => {
className="mt-2 w-full px-3 py-2 border border-[hsl(var(--border))] rounded-md bg-[hsl(var(--card))] focus:border-gray-200 focus:outline-none"
/>
</div>

</CardContent>
<CardFooter className="px-0 relative justify-end mb-6 mr-6">
<div className="mr-10">
<label htmlFor="">Gasless</label>
<Tooltip>
<TooltipTrigger>
<HelpCircleIcon className="w-4 h-4" />
</TooltipTrigger>
<TooltipContent>
<p>Active this option if you dont have enough rBTC.</p>
</TooltipContent>
</Tooltip>
<label className="flex relative items-center cursor-pointer">
<input
checked={gasless}
type="checkbox"
className="sr-only"
onChange={(e) => setGasless(Boolean(e.target.checked))}
/>
<span className="w-11 h-6 bg-card rounded-full border border-input toggle-bg"></span>
</label>
</div>
{isLoggedIn ? (
<DeployERC20TokenButton disabled={!isFormCompleted} params={formData} />
<DeployERC20TokenButton disabled={!isFormCompleted} params={formData} gasless={gasless}/>
) : (

<ConnectWalletButton title="Connect wallet to deploy" />
Expand Down
4 changes: 3 additions & 1 deletion components/ui/deployERC20TokenButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { DeployERC20Props } from '@/hooks/useDeployERC20Token'
type Props = {
disabled: boolean
params: DeployERC20Props
gasless: boolean
}

const DeployERC20TokenButton = ({disabled, params}: Props) => {
const DeployERC20TokenButton = ({disabled, params, gasless}: Props) => {
const [dialog, setDialog] = useState<boolean>(false)

return (
Expand All @@ -20,6 +21,7 @@ const DeployERC20TokenButton = ({disabled, params}: Props) => {
closeDialog={() => setDialog(false)}
open={dialog}
params={params}
gasless={gasless}
/>
)}
<Button
Expand Down
5 changes: 3 additions & 2 deletions components/ui/dialog/deployERC20TokenDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ type props = {
closeDialog: Function
open: boolean
params: DeployERC20Props
gasless: boolean
}

function DeployERC20TokenDialog({ closeDialog, open, params }: props) {
function DeployERC20TokenDialog({ closeDialog, open, params, gasless }: props) {
const { env } = useAuth();
const { deployERC20, isError, setIsError, contractAddress, txHash } = useDeployERC20Token();
const [isDeployed, setIsDeployed] = useState<boolean>(false)
Expand Down Expand Up @@ -54,7 +55,7 @@ function DeployERC20TokenDialog({ closeDialog, open, params }: props) {
setIsError(false)
try {
setTimeout(() => {
deployERC20(params)
deployERC20(params, gasless)
}, 1500)
} catch (error: any) {
setIsError(true)
Expand Down
117 changes: 90 additions & 27 deletions hooks/useDeployERC20Token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ethers } from "ethers";
import MemeTokenFactoryAbi from "@/contracts/abi/MemeTokenFactory";
import { DEPLOY_STRATEGY_ENUM } from "@/constants";
import { UploadImageIpfs } from "@/utils/PinataService";
import { sponsoredCall } from "@/utils/SponsoredCall";

type DeployERC20Params = {
name: string;
Expand All @@ -29,29 +30,96 @@ const useDeployERC20Token = () => {

const factory = new ethers.Contract(FACTORY_ADDRESS, MemeTokenFactoryAbi, signer);

const deployInflationaryToken = ({ name, symbol, initialSupply }: DeployERC20Params, cid : string) => {
const _initialSupply = ethers.parseUnits(initialSupply, 18);
return factory.createInflationaryToken(name, symbol, _initialSupply, signerAddress, cid)
const deployInflationaryToken = async (
{ name, symbol, initialSupply }: DeployERC20Params,
cid: string,
gasless: boolean
) => {
try {
const _initialSupply = ethers.parseUnits(initialSupply, 18)
if (gasless) {
const txReceipt = await sponsoredCall(
factory,
'createInflationaryToken',
[name, symbol, _initialSupply, signerAddress, cid],
FACTORY_ADDRESS
)
return txReceipt
} else {
return factory.createInflationaryToken(
name,
symbol,
_initialSupply,
signerAddress,
cid
)
}
} catch (error) {
console.log('Error deploying inflationary token:', error)
}
}

const deployDeflationaryToken = ({ name, symbol, initialSupply, maxSupply }: DeployERC20Params, cid: string) => {
const _initialSupply = ethers.parseUnits(initialSupply, 18);
const _maxSupply = ethers.parseUnits(maxSupply, 18);
return factory.createDeflationaryToken(name, symbol, _initialSupply, _maxSupply, signerAddress, cid)
const deployDeflationaryToken = async (
{ name, symbol, initialSupply, maxSupply }: DeployERC20Params,
cid: string,
gasless: boolean
) => {
try {
const _initialSupply = ethers.parseUnits(initialSupply, 18)
const _maxSupply = ethers.parseUnits(maxSupply, 18)
if (gasless) {
const txReceipt = await sponsoredCall(
factory,
'createDeflationaryToken',
[name, symbol, _initialSupply, _maxSupply, signerAddress, cid],
FACTORY_ADDRESS
)
return txReceipt
} else {
return factory.createDeflationaryToken(
name,
symbol,
_initialSupply,
_maxSupply,
signerAddress,
cid
)
}
} catch (error) {
console.log('Error deploying deflationary token:', error)
}
}

const getContractAddress = async (tx: { hash: any; wait: () => any; }) => {
const receipt = await tx.wait();

for (const log of receipt.logs) {
try {
const parsedLog = factory.interface.parseLog(log);
if (parsedLog && parsedLog.name === TOKEN_CREATED_EVENT) {
const { tokenAddress } = parsedLog.args;
return tokenAddress
const getContractAddress = async (tx: { hash: any; wait: () => any; logs?:any[]}, gasless: boolean) => {
if(gasless) {
if(!tx.logs) {
console.log('No logs found in tx');
return
}
for (const log of tx.logs) {
try {
const parsedLog = factory.interface.parseLog(log);
if (parsedLog && parsedLog.name === TOKEN_CREATED_EVENT) {
const { tokenAddress } = parsedLog.args;
return tokenAddress
}
} catch (error) {
console.error("Failed to parse log:", error);
}
}
} else {
const receipt = await tx.wait();

for (const log of receipt.logs) {
try {
const parsedLog = factory.interface.parseLog(log);
if (parsedLog && parsedLog.name === TOKEN_CREATED_EVENT) {
const { tokenAddress } = parsedLog.args;
return tokenAddress
}
} catch (error) {
console.error("Failed to parse log:", error);
}
} catch (error) {
console.error("Failed to parse log:", error);
}
}
}
Expand All @@ -61,28 +129,23 @@ const useDeployERC20Token = () => {
[DEPLOY_STRATEGY_ENUM.INFLATIONARY]: deployInflationaryToken
}

const deployERC20 = useCallback(async ({ name, symbol, maxSupply, initialSupply, strategy, image }: DeployERC20Props) => {
const deployERC20 = useCallback(async ({ name, symbol, maxSupply, initialSupply, strategy, image }: DeployERC20Props, gasless: boolean) => {
const params: DeployERC20Params = {
name,
symbol,
maxSupply,
initialSupply,
image
}
console.log('strategy is', strategy);
console.log('image exists?', !!image);

const cid = await UploadImageIpfs(image)
if(cid) {
const tx = await strategyToFunctionMapper[strategy](params, cid)
setTxHash(tx.hash)
const contractAddress = await getContractAddress(tx)
const tx = await strategyToFunctionMapper[strategy](params, cid, gasless)
setTxHash(gasless ? tx.transactionHash : tx.hash)
const contractAddress = await getContractAddress(tx, gasless)
setContractAddress(contractAddress);
} else {
console.log('Error uploading image to IPFS');
setIsError(true)
}

}, [])

return {
Expand Down
2 changes: 0 additions & 2 deletions utils/PinataService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export async function UploadImageIpfs(image: File) {
console.log('No image provided')
return null
}
console.log('Uploading image to IPFS...');
const data = new FormData()
data.append('file', image)

Expand All @@ -21,7 +20,6 @@ export async function UploadImageIpfs(image: File) {
},
}
)
console.log('IPFS Hash:', response.data.IpfsHash)
return response.data.IpfsHash
} catch (error) {
console.log('Error uploading image is: ', error)
Expand Down
29 changes: 11 additions & 18 deletions utils/SponsoredCall.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { EtherspotBundler, PrimeSdk, WalletProviderLike } from "@etherspot/prime-sdk"
import { EtherspotBundler, MetaMaskWalletProvider, PrimeSdk, WalletProviderLike } from "@etherspot/prime-sdk"
import axios from "axios"
import { ethers } from "ethers"

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

export const sponsoredCall = async (
walletProviderLike: WalletProviderLike,
contract: ethers.Contract,
functionName: string,
callParams: [],
callParams: any[],
contractAddress: string,
) => {

try {
const bundlerApiKey = process.env.BUNDLER_API_KEY
const customBundlerUrl = process.env.CUSTOM_BUNDLER_URL
const chainId = Number(process.env.CHAIN_ID)
const apiKey = process.env.ARKA_PUBLIC_KEY
const metamaskProvider = await MetaMaskWalletProvider.connect()
const bundlerApiKey = process.env.NEXT_PUBLIC_BUNDLER_API_KEY
const customBundlerUrl = process.env.NEXT_PUBLIC_CUSTOM_BUNDLER_URL
const chainId = Number(process.env.NEXT_PUBLIC_CHAIN_ID)
const apiKey = process.env.NEXT_PUBLIC_ARKA_PUBLIC_KEY
if (
!metamaskProvider ||
!bundlerApiKey ||
!customBundlerUrl ||
!chainId ||
!apiKey
) {
throw new Error('Missing data for RNSDomain claimer execution')
throw new Error(`Missing data for execution: ${metamaskProvider}, ${bundlerApiKey}, ${customBundlerUrl}, ${chainId}, ${apiKey}`)
}

const primeSdk = new PrimeSdk(walletProviderLike, {
const primeSdk = new PrimeSdk(metamaskProvider, {
chainId: chainId,
bundlerProvider: new EtherspotBundler(
chainId,
Expand All @@ -36,24 +37,18 @@ export const sponsoredCall = async (
})

const smartAddress = await primeSdk.getCounterFactualAddress();
console.log(`EtherspotWallet address: ${smartAddress}`);
const balance = await primeSdk.getNativeBalance()
console.log('balance is:', balance)
const headers = { 'Content-Type': 'application/json' }
const bodyCheckWhitelist = {
"params": [smartAddress]
}
const isWhitelisted = await axios.post(`https://arka.etherspot.io/checkWhitelist?apiKey=${apiKey}&chainId=${chainId}`, bodyCheckWhitelist, { headers: headers })
console.log('isWhitelisted:', isWhitelisted);
console.log('isWhitelisted:', isWhitelisted.data.message);

if (isWhitelisted.data.message !== "Already added") {
console.log('Whitelisting address');
const body = {
"params": [[smartAddress]]
}
const responseWhitelist = await axios.post(`https://arka.etherspot.io/whitelist?apiKey=${apiKey}&chainId=${chainId}`, body, { headers: headers })
console.log('responseWhitelist:', responseWhitelist);
}
const encodedData = contract.interface.encodeFunctionData(
functionName,
Expand All @@ -74,12 +69,10 @@ export const sponsoredCall = async (
const timeout = Date.now() + 180000 // 3 minutes timeout

while (userOpsReceipt == null && Date.now() < timeout) {
console.log('Waiting for transaction...')
await wait(5000)
userOpsReceipt = await primeSdk.getUserOpReceipt(uoHash)
}
console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt)
return userOpsReceipt
return userOpsReceipt.receipt
} catch (error) {
console.log('error: ', error)
throw new Error('Error executing sponsored call: ' + error)
Expand Down

0 comments on commit 61abadb

Please sign in to comment.