diff --git a/README.md b/README.md index 52f667d9..e136073c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The `.contract` file combines the Wasm and metadata into one file and can be use ### Commands * `pnpm erc20` +* `pnpm erc721` * `pnpm basic-contract-caller` * `pnpm contract-terminate` * `pnpm contract-transfer` diff --git a/erc721/frontend/.gitignore b/erc721/frontend/.gitignore new file mode 100644 index 00000000..c13f37b6 --- /dev/null +++ b/erc721/frontend/.gitignore @@ -0,0 +1,21 @@ +# Logs +logs +*.log +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/erc721/frontend/README.md b/erc721/frontend/README.md new file mode 100644 index 00000000..ad88c572 --- /dev/null +++ b/erc721/frontend/README.md @@ -0,0 +1,4 @@ +# Have Questions? + +For any questions about building front end applications with [useink](https://use.ink/frontend/overview/), join the [Element chat](https://matrix.to/#/%23useink:parity.io). + diff --git a/erc721/frontend/assets/erc721.json b/erc721/frontend/assets/erc721.json new file mode 100644 index 00000000..caf458b2 --- /dev/null +++ b/erc721/frontend/assets/erc721.json @@ -0,0 +1,1086 @@ +{ + "source": { + "hash": "0xe27214c33419121ce913d12859f57807fcee75a5ca53c32e7c97dd5d48181507", + "language": "ink! 4.2.0", + "compiler": "rustc 1.71.0", + "build_info": { + "build_mode": "Release", + "cargo_contract_version": "2.2.1", + "rust_toolchain": "stable-x86_64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "erc721", + "version": "4.2.0", + "authors": [ + "Parity Technologies " + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [ + "Creates a new ERC-721 token contract." + ], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 5 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 0 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 15 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 3 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 18 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 16 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 17 + } + }, + "events": [ + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "from", + "type": { + "displayName": [ + "Option" + ], + "type": 9 + } + }, + { + "docs": [], + "indexed": true, + "label": "to", + "type": { + "displayName": [ + "Option" + ], + "type": 9 + } + }, + { + "docs": [], + "indexed": true, + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "docs": [ + "Event emitted when a token transfer occurs." + ], + "label": "Transfer" + }, + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "from", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "docs": [], + "indexed": true, + "label": "to", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "docs": [], + "indexed": true, + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "docs": [ + "Event emitted when a token approve occurs." + ], + "label": "Approval" + }, + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "docs": [], + "indexed": true, + "label": "operator", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "docs": [], + "indexed": false, + "label": "approved", + "type": { + "displayName": [ + "bool" + ], + "type": 11 + } + } + ], + "docs": [ + "Event emitted when an operator is enabled or disabled for an owner.", + "The operator can manage all NFTs of the owner." + ], + "label": "ApprovalForAll" + } + ], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 6 + }, + "messages": [ + { + "args": [ + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + } + ], + "default": false, + "docs": [ + " Returns the balance of the owner.", + "", + " This represents the amount of unique tokens the owner has." + ], + "label": "balance_of", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0x0f755a56" + }, + { + "args": [ + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Returns the owner of the token." + ], + "label": "owner_of", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 8 + }, + "selector": "0x99720c1e" + }, + { + "args": [ + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Returns the approved account ID for this token if any." + ], + "label": "get_approved", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 8 + }, + "selector": "0x27592dea" + }, + { + "args": [ + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "operator", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + } + ], + "default": false, + "docs": [ + " Returns `true` if the operator is approved by the owner." + ], + "label": "is_approved_for_all", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0x0f5922e9" + }, + { + "args": [ + { + "label": "to", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "approved", + "type": { + "displayName": [ + "bool" + ], + "type": 11 + } + } + ], + "default": false, + "docs": [ + " Approves or disapproves the operator for all tokens of the caller." + ], + "label": "set_approval_for_all", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xcfd0c27b" + }, + { + "args": [ + { + "label": "to", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Approves the account to transfer the specified token on behalf of the caller." + ], + "label": "approve", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x681266a0" + }, + { + "args": [ + { + "label": "destination", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Transfers the token from the caller to the given destination." + ], + "label": "transfer", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x84a15da1" + }, + { + "args": [ + { + "label": "from", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "to", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Transfer approved or owned token." + ], + "label": "transfer_from", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x0b396f18" + }, + { + "args": [ + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Creates a new token." + ], + "label": "mint", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xcfdd9aa2" + }, + { + "args": [ + { + "label": "id", + "type": { + "displayName": [ + "TokenId" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [ + " Deletes an existing token. Only the owner can burn the token." + ], + "label": "burn", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xb1efc17b" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0xed16db9e", + "ty": 0 + } + }, + "root_key": "0xed16db9e" + } + }, + "name": "token_owner" + }, + { + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0x4d897660", + "ty": 0 + } + }, + "root_key": "0x4d897660" + } + }, + "name": "token_approvals" + }, + { + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0xb5379df2", + "ty": 3 + } + }, + "root_key": "0xb5379df2" + } + }, + "name": "owned_tokens_count" + }, + { + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0xad984333", + "ty": 4 + } + }, + "root_key": "0xad984333" + } + }, + "name": "operator_approvals" + } + ], + "name": "Erc721" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "array": { + "len": 32, + "type": 2 + } + } + } + }, + { + "id": 2, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 3, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 4, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 4 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 4 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 9 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 9 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 9, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "None" + }, + { + "fields": [ + { + "type": 0 + } + ], + "index": 1, + "name": "Some" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + } + ], + "path": [ + "Option" + ] + } + }, + { + "id": 10, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 11 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 11 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 12, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 13 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 13 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 13, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 4 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 14 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 4 + }, + { + "name": "E", + "type": 14 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 14, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "NotOwner" + }, + { + "index": 1, + "name": "NotApproved" + }, + { + "index": 2, + "name": "TokenExists" + }, + { + "index": 3, + "name": "TokenNotFound" + }, + { + "index": 4, + "name": "CannotInsert" + }, + { + "index": 5, + "name": "CannotFetchValue" + }, + { + "index": 6, + "name": "NotAllowed" + } + ] + } + }, + "path": [ + "erc721", + "erc721", + "Error" + ] + } + }, + { + "id": 15, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 16, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 17, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 18, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/erc721/frontend/index.html b/erc721/frontend/index.html new file mode 100644 index 00000000..e4d2ac59 --- /dev/null +++ b/erc721/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + ink! Examples + + +
+ + + diff --git a/erc721/frontend/package.json b/erc721/frontend/package.json new file mode 100644 index 00000000..91571bde --- /dev/null +++ b/erc721/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "erc721", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "ui": "workspace:ui@*" + }, + "devDependencies": { + "@types/react": "^18.0.37", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.14", + "eslint": "^8.38.0", + "postcss": "^8.4.24", + "tailwindcss": "^3.3.2", + "typescript": "^5.0.2", + "vite": "^4.3.9" + } +} diff --git a/erc721/frontend/postcss.config.js b/erc721/frontend/postcss.config.js new file mode 100644 index 00000000..2aa7205d --- /dev/null +++ b/erc721/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/erc721/frontend/public/logo.svg b/erc721/frontend/public/logo.svg new file mode 100644 index 00000000..c31ded82 --- /dev/null +++ b/erc721/frontend/public/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/erc721/frontend/src/App.tsx b/erc721/frontend/src/App.tsx new file mode 100644 index 00000000..a8bb2954 --- /dev/null +++ b/erc721/frontend/src/App.tsx @@ -0,0 +1,26 @@ +import metadata from '../assets/erc721.json'; +import { Erc721 } from './components'; +import { DeployerProvider, InkLayout } from 'ui'; + +function App() { + return ( + + + + + + ); +} + +export default App; diff --git a/erc721/frontend/src/Global.css b/erc721/frontend/src/Global.css new file mode 100644 index 00000000..c2f5c12e --- /dev/null +++ b/erc721/frontend/src/Global.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + background-color: #1A1452; +} \ No newline at end of file diff --git a/erc721/frontend/src/components/Erc721/Erc721.tsx b/erc721/frontend/src/components/Erc721/Erc721.tsx new file mode 100644 index 00000000..b96c9329 --- /dev/null +++ b/erc721/frontend/src/components/Erc721/Erc721.tsx @@ -0,0 +1,65 @@ +import metadata from '../../../assets/erc721.json'; +import { ReadView } from '../ReadView'; +import { WriteView } from '../WriteView'; +import { useState } from 'react'; +import { Card, Tab, Tabs, formatContractName, useDeployerState } from 'ui'; +import { useCallSubscription, useContract, useTx, useWallet } from 'useink'; +import { useTxNotifications } from 'useink/notifications'; +import { pickDecoded } from 'useink/utils'; + +export const Erc721: React.FC = () => { + const { contractAddress } = useDeployerState(); + const erc721 = useContract(contractAddress || '', metadata); + const [view, setView] = useState<'read' | 'write'>('read'); + + const approve = useTx(erc721, 'approve'); + useTxNotifications(approve); + + const { account } = useWallet(); + const balanceOf = useCallSubscription(erc721, 'balanceOf', [ + account?.address || '', + ]); + + return ( +
+ +
+

+ {formatContractName(metadata.contract.name)} +

+ + {erc721 && account && ( +
+

Your Balance

+

+ {pickDecoded(balanceOf.result) || '--'} +

+
+ )} +
+ + {erc721 && ( + <> + + setView('read')} isSelected={view === 'read'}> + Read + + setView('write')} + isSelected={view === 'write'} + > + Write + + + + {'read' === view ? ( + + ) : ( + + )} + + )} +
+
+ ); +}; diff --git a/erc721/frontend/src/components/Erc721/index.ts b/erc721/frontend/src/components/Erc721/index.ts new file mode 100644 index 00000000..c87713b7 --- /dev/null +++ b/erc721/frontend/src/components/Erc721/index.ts @@ -0,0 +1 @@ +export * from './Erc721'; diff --git a/erc721/frontend/src/components/ReadView/ReadView.tsx b/erc721/frontend/src/components/ReadView/ReadView.tsx new file mode 100644 index 00000000..a4aefd29 --- /dev/null +++ b/erc721/frontend/src/components/ReadView/ReadView.tsx @@ -0,0 +1,161 @@ +import { useState } from 'react'; +import { Button, InputField, NumberInput } from 'ui'; +import { ChainContract, useCall } from 'useink'; +import { pickDecoded, pickError } from 'useink/utils'; + +interface Props { + erc721: ChainContract; +} + +export const ReadView: React.FC = ({ erc721 }) => { + const ownerOf = useCall(erc721, 'ownerOf'); + const [ownerOfId, setOwnerOfId] = useState(1); + + const balanceOf = useCall(erc721, 'balanceOf'); + const [balanceOfOwner, setBalOfOwner] = useState(''); + + const getApproved = useCall(erc721, 'getApproved'); + const [approvedTokenId, setApprovedTokenId] = useState(1); + + const isApprovedForAll = useCall(erc721, 'isApprovedForAll'); + const [approvedForAllOwner, setApprovedForAllOwner] = useState(''); + const [approvedForAllOperator, setApprovedForAllOperator] = useState(''); + + return ( +
+
+ + + + + {pickDecoded(ownerOf.result) && ( +

+ {pickDecoded(ownerOf.result)} +

+ )} + + {pickError(ownerOf.result) && ( +

+ {pickError(ownerOf.result)} +

+ )} +
+ +
+ + setBalOfOwner(e.target.value)} + placeholder='Enter an Address...' + disabled={balanceOf.isSubmitting} + /> + + + {balanceOf !== undefined && ( +

+ {pickDecoded(balanceOf.result)} +

+ )} +
+ +
+ + + + + {pickDecoded(getApproved.result) && ( +

+ {pickDecoded(getApproved.result)} +

+ )} + + {pickError(getApproved.result) && ( +

+ {pickError(getApproved.result)} +

+ )} +
+ +
+ + setApprovedForAllOwner(e.target.value)} + placeholder='Enter the owner...' + disabled={isApprovedForAll.isSubmitting} + /> + + setApprovedForAllOperator(e.target.value)} + placeholder='Enter the operator...' + disabled={isApprovedForAll.isSubmitting} + /> + + + {pickDecoded(isApprovedForAll.result) !== undefined && ( +

+ {`${pickDecoded(isApprovedForAll.result)}`} +

+ )} + + {pickError(isApprovedForAll.result) && ( +

+ {pickError(isApprovedForAll.result)} +

+ )} +
+
+ ); +}; diff --git a/erc721/frontend/src/components/ReadView/index.ts b/erc721/frontend/src/components/ReadView/index.ts new file mode 100644 index 00000000..c4729ab7 --- /dev/null +++ b/erc721/frontend/src/components/ReadView/index.ts @@ -0,0 +1 @@ +export * from './ReadView'; diff --git a/erc721/frontend/src/components/WriteView/WriteView.tsx b/erc721/frontend/src/components/WriteView/WriteView.tsx new file mode 100644 index 00000000..48b7d400 --- /dev/null +++ b/erc721/frontend/src/components/WriteView/WriteView.tsx @@ -0,0 +1,252 @@ +import { useState } from 'react'; +import { + Button, + ConnectButton, + InputField, + NumberInput, + ToggleSwitch, +} from 'ui'; +import { ChainContract, useTx, useWallet } from 'useink'; +import { useTxNotifications } from 'useink/notifications'; +import { isPendingSignature, shouldDisable } from 'useink/utils'; + +interface Props { + erc721: ChainContract; +} + +export const WriteView: React.FC = ({ erc721 }) => { + const { account } = useWallet(); + + const [mintTokenId, setMintTokenId] = useState(1); + const mint = useTx(erc721, 'mint'); + useTxNotifications(mint); + + const [burnTokenId, setBurnTokenId] = useState(1); + const burn = useTx(erc721, 'burn'); + useTxNotifications(burn); + + const [isApprovedForAll, setIsApprovedForAll] = useState(true); + const [approvalForAllAccount, setApprovalForAllAccount] = useState(''); + const setApprovalForAll = useTx(erc721, 'setApprovalForAll'); + useTxNotifications(setApprovalForAll); + + const [approveTokenId, setApproveTokenId] = useState(1); + const [approvalForTokenAccount, setApprovalForTokenAccount] = useState(''); + const approve = useTx(erc721, 'approve'); + useTxNotifications(approve); + + const [transferTokenId, setTransferTokenId] = useState(1); + const [transferToAccount, setTransferToAccount] = useState(''); + const transfer = useTx(erc721, 'transfer'); + useTxNotifications(transfer); + + const [transferFromTokenId, setTransferFromTokenId] = useState(1); + const [transferFromOwnerAccount, setTransferFromOwnerAccount] = useState(''); + const [transferFromToAccount, setTransferFromToAccount] = useState(''); + const transferFrom = useTx(erc721, 'transferFrom'); + useTxNotifications(transferFrom); + + if (!account) return ; + + return ( +
+
+ + + +
+ +
+ + + +
+ +
+ + setIsApprovedForAll(!isApprovedForAll)} + /> + + + setApprovalForAllAccount(e.target.value)} + placeholder='Enter an Address...' + disabled={shouldDisable(setApprovalForAll)} + /> + +
+ +
+ + + + + setApprovalForTokenAccount(e.target.value)} + placeholder='Enter an Address...' + disabled={shouldDisable(approve)} + /> + +
+ +
+ + + + + setTransferToAccount(e.target.value)} + placeholder='Enter an Address...' + disabled={shouldDisable(transfer)} + /> + +
+ +
+ + + + + setTransferFromOwnerAccount(e.target.value)} + placeholder='Enter an Address...' + disabled={shouldDisable(transferFrom)} + /> + + + setTransferFromToAccount(e.target.value)} + placeholder='Enter an Address...' + disabled={shouldDisable(transferFrom)} + /> + +
+
+ ); +}; diff --git a/erc721/frontend/src/components/WriteView/index.ts b/erc721/frontend/src/components/WriteView/index.ts new file mode 100644 index 00000000..98738aa5 --- /dev/null +++ b/erc721/frontend/src/components/WriteView/index.ts @@ -0,0 +1 @@ +export * from './WriteView'; diff --git a/erc721/frontend/src/components/index.ts b/erc721/frontend/src/components/index.ts new file mode 100644 index 00000000..c87713b7 --- /dev/null +++ b/erc721/frontend/src/components/index.ts @@ -0,0 +1 @@ +export * from './Erc721'; diff --git a/erc721/frontend/src/main.tsx b/erc721/frontend/src/main.tsx new file mode 100644 index 00000000..1f69b1a2 --- /dev/null +++ b/erc721/frontend/src/main.tsx @@ -0,0 +1,27 @@ +import metadata from '../assets/erc721.json'; +import App from './App.tsx'; +import './Global.css'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import 'ui/style.css'; +import { UseInkProvider } from 'useink'; +import { RococoContractsTestnet } from 'useink/chains'; +import { NotificationsProvider } from 'useink/notifications'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + + + , +); diff --git a/erc721/frontend/src/vite-env.d.ts b/erc721/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/erc721/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/erc721/frontend/tailwind.config.js b/erc721/frontend/tailwind.config.js new file mode 100644 index 00000000..c120a39a --- /dev/null +++ b/erc721/frontend/tailwind.config.js @@ -0,0 +1,2 @@ +import config from '../../ui/tailwind.config'; +export default config; \ No newline at end of file diff --git a/erc721/frontend/tsconfig.json b/erc721/frontend/tsconfig.json new file mode 100644 index 00000000..5178ef59 --- /dev/null +++ b/erc721/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "../../ui/src/contexts/DeployerContext"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/erc721/frontend/tsconfig.node.json b/erc721/frontend/tsconfig.node.json new file mode 100644 index 00000000..42872c59 --- /dev/null +++ b/erc721/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/erc721/frontend/vite.config.ts b/erc721/frontend/vite.config.ts new file mode 100644 index 00000000..4e7004eb --- /dev/null +++ b/erc721/frontend/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/package.json b/package.json index 0c0859a9..78d7b276 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "contract_terminate": "pnpm --filter ui dev & pnpm --filter contract_terminate dev", "contract_transfer": "pnpm --filter ui dev & pnpm --filter contract_transfer dev", "erc20": "pnpm --filter ui dev & pnpm --filter erc20 dev", + "erc721": "pnpm --filter ui dev & pnpm --filter erc721 dev", "flipper": "pnpm --filter ui dev & pnpm --filter flipper dev", "incrementer": "pnpm --filter ui dev & pnpm --filter incrementer dev" }, diff --git a/ui/src/ToggleSwitch/ToggleSwitch.tsx b/ui/src/ToggleSwitch/ToggleSwitch.tsx new file mode 100644 index 00000000..cc613fcb --- /dev/null +++ b/ui/src/ToggleSwitch/ToggleSwitch.tsx @@ -0,0 +1,40 @@ +import { Switch } from '@headlessui/react'; +import classNames from 'classnames'; +import React from 'react'; + +interface Props { + enabled: boolean; + handleClick: () => void; + screenReader?: string; +} + +export const ToggleSwitch: React.FC = ({ + enabled, + handleClick: handleClose, + screenReader, +}) => ( +
+ + {screenReader && {screenReader}} + +
+); diff --git a/ui/src/ToggleSwitch/index.ts b/ui/src/ToggleSwitch/index.ts new file mode 100644 index 00000000..abd33f3c --- /dev/null +++ b/ui/src/ToggleSwitch/index.ts @@ -0,0 +1 @@ +export * from './ToggleSwitch'; diff --git a/ui/src/index.ts b/ui/src/index.ts index 681c9450..c5bce20a 100644 --- a/ui/src/index.ts +++ b/ui/src/index.ts @@ -17,6 +17,7 @@ export * from './NumberInput'; export * from './RunResults'; export * from './Snackbar'; export * from './Tabs'; +export * from './ToggleSwitch'; export * from './contexts'; export * from './hooks';