diff --git a/examples/README.md b/examples/README.md index b639fbb..9df2e80 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,7 +18,7 @@ The `examples/` directory is a growing & living folder, and open for contributio - [ ] Multicall - [ ] Call - [ ] Events - - [ ] Simulating Method Calls + - [x] Simulating Method Calls - Filters & Logs - [ ] Blocks - [ ] Pending Transactions diff --git a/examples/contracts_simulating_contract/README.md b/examples/contracts_simulating_contract/README.md new file mode 100644 index 0000000..40b68e7 --- /dev/null +++ b/examples/contracts_simulating_contract/README.md @@ -0,0 +1,3 @@ +# simulating contract Example + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/iosh/cive/tree/main/examples/contracts_simulating_contract) \ No newline at end of file diff --git a/examples/contracts_simulating_contract/index.html b/examples/contracts_simulating_contract/index.html new file mode 100644 index 0000000..dfdcee0 --- /dev/null +++ b/examples/contracts_simulating_contract/index.html @@ -0,0 +1,14 @@ + + + + + + + +

simulating contract Example

+
Loading...
+ + + diff --git a/examples/contracts_simulating_contract/index.tsx b/examples/contracts_simulating_contract/index.tsx new file mode 100644 index 0000000..41de479 --- /dev/null +++ b/examples/contracts_simulating_contract/index.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './src/App' +import 'bulma/css/bulma.css' +ReactDOM.createRoot(document.getElementById('app') as HTMLElement).render( + + + , +) diff --git a/examples/contracts_simulating_contract/package.json b/examples/contracts_simulating_contract/package.json new file mode 100644 index 0000000..bf3f841 --- /dev/null +++ b/examples/contracts_simulating_contract/package.json @@ -0,0 +1,21 @@ +{ + "name": "contracts_simulating_contract", + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "bulma": "^1.0.2", + "cive": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.8", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.6.2", + "vite": "^5.4.4" + } +} diff --git a/examples/contracts_simulating_contract/src/App.tsx b/examples/contracts_simulating_contract/src/App.tsx new file mode 100644 index 0000000..eff2b0c --- /dev/null +++ b/examples/contracts_simulating_contract/src/App.tsx @@ -0,0 +1,192 @@ +import { + http, + type Address, + type Hash, + type SimulateContractErrorType, + type SimulateContractReturnType, + type WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptReturnType, + createPublicClient, + createWalletClient, + custom, + parseUnits, +} from 'cive' +import { mainnet, testnet } from 'cive/chains' +import { useCallback, useState } from 'react' +import 'cive/window' +import contract from './contract' + +const CONTRACT_ADDRESS = 'cfxtest:acfrcwu7yn4ysjybux326my6a743zw2zwjps5had1g' + +const client = createPublicClient({ + chain: testnet, + transport: http(), +}) +const walletClient = createWalletClient({ + chain: testnet, + transport: custom(window.fluent!), +}) +export default function App() { + const [account, setAccount] = useState
() + const [mint, setMint] = + useState< + SimulateContractReturnType< + (typeof contract)['abi'], + 'mint', + [Address, bigint] + > + >() + const [approve, setApprove] = + useState< + SimulateContractReturnType< + (typeof contract)['abi'], + 'approve', + [Address, bigint] + > + >() + const [error, setError] = useState() + + const connect = useCallback(async () => { + const [address] = await walletClient.requestAddresses() + setAccount(address) + }, []) + + const handleSimulateMint = useCallback(async () => { + setError('') + if (!account) return + try { + const result = await client.simulateContract({ + account, + address: CONTRACT_ADDRESS, + abi: contract.abi, + functionName: 'mint', + args: [account, parseUnits('1', 18)], + }) + setMint(result) + } catch (e: unknown) { + const err = e as SimulateContractErrorType + + setError(err.name) + } + }, [account]) + + const handleSimulateApprove = useCallback(async () => { + setError('') + if (!account) return + try { + const result = await client.simulateContract({ + account, + address: CONTRACT_ADDRESS, + abi: contract.abi, + functionName: 'approve', + args: [account, parseUnits('1', 18)], + }) + setApprove(result) + } catch (e: unknown) { + const err = e as SimulateContractErrorType + setError(err.name) + } + }, [account]) + return ( +
+
+ {account ? ( +
+ Account: {account} +
+
+ + Result: {`${mint?.result}`} + + {mint?.request && ( +
+

Simulate Mint request:

+ + + + + + + + + + {mint?.request && + Object.entries(mint?.request).map( + ([key, value], idx) => ( + + + + + + ), + )} + +
#KeyValue
{idx}{key}{`${value}`}
+
+ )} +
+
+ +
+
+ + + + Result: {`${approve?.result}`} + +

+ you can use the simulateContract to get the contract's return + value +

+ + {approve?.request && ( +
+

Simulate Approve request:

+ + + + + + + + + + {approve?.request && + Object.entries(approve?.request).map( + ([key, value], idx) => ( + + + + + + ), + )} + +
#KeyValue
{idx}{key}{`${value}`}
+

+ you can use the request to call writeContract function +

+
+ )} +
+
+ + {error && ( +
+

Error: {error}

+
+ )} +
+ ) : ( + + )} +
+
+ ) +} diff --git a/examples/contracts_simulating_contract/src/contract.ts b/examples/contracts_simulating_contract/src/contract.ts new file mode 100644 index 0000000..deb692d --- /dev/null +++ b/examples/contracts_simulating_contract/src/contract.ts @@ -0,0 +1,240 @@ +export default { + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'allowance', type: 'uint256' }, + { internalType: 'uint256', name: 'needed', type: 'uint256' }, + ], + name: 'ERC20InsufficientAllowance', + type: 'error', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'balance', type: 'uint256' }, + { internalType: 'uint256', name: 'needed', type: 'uint256' }, + ], + name: 'ERC20InsufficientBalance', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'approver', type: 'address' }], + name: 'ERC20InvalidApprover', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'receiver', type: 'address' }], + name: 'ERC20InvalidReceiver', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'ERC20InvalidSender', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'spender', type: 'address' }], + name: 'ERC20InvalidSpender', + type: 'error', + }, + { inputs: [], name: 'EnforcedPause', type: 'error' }, + { inputs: [], name: 'ExpectedPause', type: 'error' }, + { + 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: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Paused', + 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', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Unpaused', + 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: 'value', 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: [{ internalType: 'uint256', name: 'value', type: 'uint256' }], + name: 'burn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'burnFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'pause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'paused', + 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: 'value', 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: 'value', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unpause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/examples/contracts_simulating_contract/tsconfig.json b/examples/contracts_simulating_contract/tsconfig.json new file mode 100644 index 0000000..f0a2350 --- /dev/null +++ b/examples/contracts_simulating_contract/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/contracts_simulating_contract/vite.config.ts b/examples/contracts_simulating_contract/vite.config.ts new file mode 100644 index 0000000..36f7f4e --- /dev/null +++ b/examples/contracts_simulating_contract/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/contracts_writing_contract/src/App.tsx b/examples/contracts_writing_contract/src/App.tsx index fcfdc20..867ed83 100644 --- a/examples/contracts_writing_contract/src/App.tsx +++ b/examples/contracts_writing_contract/src/App.tsx @@ -1,13 +1,13 @@ import { http, + type Address, + type Hash, + type SimulateContractErrorType, + type WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptReturnType, createPublicClient, createWalletClient, custom, - Address, - Hash, - WaitForTransactionReceiptReturnType, - WaitForTransactionReceiptErrorType, - SimulateContractErrorType, } from 'cive' import { mainnet, testnet } from 'cive/chains' import { useCallback, useState } from 'react' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1988bbe..eda896c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -233,6 +233,37 @@ importers: specifier: ^5.4.4 version: 5.4.4(@types/node@20.14.10) + examples/contracts_simulating_contract: + dependencies: + bulma: + specifier: ^1.0.2 + version: 1.0.2 + cive: + specifier: latest + version: 0.4.1(typescript@5.6.2)(zod@3.23.8) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.8 + version: 18.3.9 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.4.4(@types/node@20.14.10)) + typescript: + specifier: ^5.6.2 + version: 5.6.2 + vite: + specifier: ^5.4.4 + version: 5.4.4(@types/node@20.14.10) + examples/contracts_writing_contract: dependencies: bulma: