diff --git a/examples/README.md b/examples/README.md index bdeffcc..b639fbb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,7 +14,7 @@ The `examples/` directory is a growing & living folder, and open for contributio - Contracts - [x] Deploying - [x] Reading - - [ ] Writing + - [x] Writing - [ ] Multicall - [ ] Call - [ ] Events diff --git a/examples/contracts_writing_contract/README.md b/examples/contracts_writing_contract/README.md new file mode 100644 index 0000000..b9b2bdb --- /dev/null +++ b/examples/contracts_writing_contract/README.md @@ -0,0 +1,3 @@ +# Writing 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_writing_contract) \ No newline at end of file diff --git a/examples/contracts_writing_contract/index.html b/examples/contracts_writing_contract/index.html new file mode 100644 index 0000000..a72bedd --- /dev/null +++ b/examples/contracts_writing_contract/index.html @@ -0,0 +1,14 @@ + + + + + + + +

Writing Contract Example

+
Loading...
+ + + diff --git a/examples/contracts_writing_contract/index.tsx b/examples/contracts_writing_contract/index.tsx new file mode 100644 index 0000000..41de479 --- /dev/null +++ b/examples/contracts_writing_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_writing_contract/package.json b/examples/contracts_writing_contract/package.json new file mode 100644 index 0000000..0f84c90 --- /dev/null +++ b/examples/contracts_writing_contract/package.json @@ -0,0 +1,21 @@ +{ + "name": "contracts_writing_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_writing_contract/src/App.tsx b/examples/contracts_writing_contract/src/App.tsx new file mode 100644 index 0000000..fcfdc20 --- /dev/null +++ b/examples/contracts_writing_contract/src/App.tsx @@ -0,0 +1,138 @@ +import { + http, + createPublicClient, + createWalletClient, + custom, + Address, + Hash, + WaitForTransactionReceiptReturnType, + WaitForTransactionReceiptErrorType, + SimulateContractErrorType, +} 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 [hash, setHash] = useState() + const [receipt, setReceipt] = useState() + const [error, setError] = useState() + + const connect = useCallback(async () => { + const [address] = await walletClient.requestAddresses() + setAccount(address) + }, []) + + const mint = useCallback(async () => { + setError('') + if (!account) return + try { + const { request } = await client.simulateContract({ + account, + address: CONTRACT_ADDRESS, + abi: contract.abi, + functionName: 'mint', + args: [account, 1n * 10n ** 18n], + }) + // writeContract before simulateContract first + const hash = await walletClient.writeContract(request) + setHash(hash) + + client + .waitForTransactionReceipt({ hash, retryCount: 11 }) + .then((receipt) => { + setReceipt(receipt) + }) + } catch (e: unknown) { + const err = e as + | WaitForTransactionReceiptErrorType + | SimulateContractErrorType + + setError(err.name) + } + }, [account]) + + return ( +
+
+ {account ? ( +
+ Account: {account} +
+
+ + result: +
+
+
+
+ + {hash ? `transaction hash : ${hash}` : ''} + +
+
+ + {hash && ( +
+
+ + wait for transaction receipt + +
+
+ )} + + {receipt && ( +
+

Transaction receipt:

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

Error: {error}

+
+ )} +
+ ) : ( + + )} +
+
+ ) +} diff --git a/examples/contracts_writing_contract/src/contract.ts b/examples/contracts_writing_contract/src/contract.ts new file mode 100644 index 0000000..deb692d --- /dev/null +++ b/examples/contracts_writing_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_writing_contract/tsconfig.json b/examples/contracts_writing_contract/tsconfig.json new file mode 100644 index 0000000..f0a2350 --- /dev/null +++ b/examples/contracts_writing_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_writing_contract/vite.config.ts b/examples/contracts_writing_contract/vite.config.ts new file mode 100644 index 0000000..36f7f4e --- /dev/null +++ b/examples/contracts_writing_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/pnpm-lock.yaml b/pnpm-lock.yaml index 4262d8d..1988bbe 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_writing_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/epoch_watch_epoch_number: dependencies: bulma: