diff --git a/app/(general)/integration/safe/create/page.tsx b/app/(general)/integration/safe/create/page.tsx new file mode 100644 index 00000000..24497be9 --- /dev/null +++ b/app/(general)/integration/safe/create/page.tsx @@ -0,0 +1,14 @@ +// import { WalletConnect } from '@/components/blockchain/wallet-connect' +import { FormDeploySafe } from '@/integrations/safe/components/form-deploy-safe' + +export default function PageIntegration() { + // use safe hook + return ( +
+
+ {/* */} + +
+
+ ) +} diff --git a/app/(general)/integration/safe/layout.tsx b/app/(general)/integration/safe/layout.tsx new file mode 100644 index 00000000..5f6770ff --- /dev/null +++ b/app/(general)/integration/safe/layout.tsx @@ -0,0 +1,76 @@ +'use client' +import { ReactNode } from 'react' + +import { motion } from 'framer-motion' +import Image from 'next/image' +import { usePathname } from 'next/navigation' +import Balancer from 'react-wrap-balancer' + +import { IsDarkTheme } from '@/components/shared/is-dark-theme' +import { IsLightTheme } from '@/components/shared/is-light-theme' +import { LinkComponent } from '@/components/shared/link-component' +import { FADE_DOWN_ANIMATION_VARIANTS } from '@/config/design' +import { turboIntegrations } from '@/data/turbo-integrations' +import { SafeAppProvider } from '@/integrations/safe/safe-provider' +import { cn } from '@/lib/utils' + +const integrationData = turboIntegrations.safe + +const createPath = '/integration/safe/create' +const managePath = '/integration/safe/manage' + +export default function LayoutIntegration({ children }: { children: ReactNode }) { + const pathname = usePathname() + + return ( + +
+ + + Starter logo + + + Starter logo + + + {integrationData.name} + + + {integrationData.description} + + + + Documentation + + + + + + + + + + + +
{children}
+
+
+
+
+ ) +} diff --git a/app/(general)/integration/safe/manage/page.tsx b/app/(general)/integration/safe/manage/page.tsx new file mode 100644 index 00000000..a0b2f42f --- /dev/null +++ b/app/(general)/integration/safe/manage/page.tsx @@ -0,0 +1,21 @@ +// import { WalletConnect } from '@/components/blockchain/wallet-connect' +import { FormSendSafeTransaction } from '@/integrations/safe/components/form-send-safe-transaction' +import { ManageSafe } from '@/integrations/safe/components/manage-safe' +import { PendingSafeTransactions } from '@/integrations/safe/components/pending-safe-transactions' +import { ConnectSafe } from '@/integrations/safe/hooks/use-connect-safe' + +export default function PageIntegration() { + return ( +
+
+ {/* */} + + + + + {/* */} + +
+
+ ) +} diff --git a/app/(general)/integration/safe/opengraph-image.tsx b/app/(general)/integration/safe/opengraph-image.tsx new file mode 100644 index 00000000..f6d0fb6a --- /dev/null +++ b/app/(general)/integration/safe/opengraph-image.tsx @@ -0,0 +1,9 @@ +import { IntegrationOgImage } from "@/components/ui/social/og-image-integrations" + +export const runtime = "edge" +export const size = { + width: 1200, + height: 630, +} + +export default IntegrationOgImage("safe") diff --git a/app/(general)/integration/safe/page.tsx b/app/(general)/integration/safe/page.tsx new file mode 100644 index 00000000..7c6c03be --- /dev/null +++ b/app/(general)/integration/safe/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation' + +export default function PageIntegration() { + redirect('/integration/safe/create') +} diff --git a/app/(general)/integration/safe/twitter-image.tsx b/app/(general)/integration/safe/twitter-image.tsx new file mode 100644 index 00000000..215a2a59 --- /dev/null +++ b/app/(general)/integration/safe/twitter-image.tsx @@ -0,0 +1,9 @@ +import Image from "./opengraph-image" + +export const runtime = "edge" +export const size = { + width: 1200, + height: 630, +} + +export default Image diff --git a/data/turbo-integrations.ts b/data/turbo-integrations.ts index 4d26d3ff..705ed7e9 100644 --- a/data/turbo-integrations.ts +++ b/data/turbo-integrations.ts @@ -131,6 +131,15 @@ export const turboIntegrations = { imgDark: "/integrations/connext.png", category: "protocols", }, + safe: { + name: 'Safe', + href: '/integration/safe', + url: 'https://docs.safe.global/', + description: 'Safe is the most trusted decentralized custody protocol and collective asset management platform on Ethereum and the EVM', + imgLight: '/integrations/safe-light.svg', + imgDark: '/integrations/safe-dark.svg', + category: "protocols", + }, gelato: { name: "Gelato", href: "/integration/gelato", diff --git a/integrations/safe/README.md b/integrations/safe/README.md new file mode 100644 index 00000000..b416e11d --- /dev/null +++ b/integrations/safe/README.md @@ -0,0 +1,86 @@ +# Safe TurboETH Integration + +This TurboETH integration provides a suite of hooks and components to facilitate interaction with ERC1155 contracts. In consideration of the numerous extensions and features offered by ERC1155, this integration is based on a contract from [solidstate](https://github.com/solidstate-network/solidstate-solidity), incorporating the following features and access controls: + +**Features:** + +- Mintable +- Enumerable +- ERC1155BaseStorage +- Single Approve + +**Access Control:** + +- Ownable + +## ABI and Bytecode + +Within the `artifacts` directory, you will find the `erc1155-abi.ts` file, containing the ABI from the contract that was used to generate the [Wagmi CLI](https://wagmi.sh/cli/getting-started) contract hooks. The `erc1155-bytecode.ts` file contains the bytecode used for contract deployment. If you wish to use a different ERC1155 contract with distinct features/extensions, simply update the ABI and bytecode, and then regenerate the Wagmi CLI hooks using the command `pnpm wagmi generate`. + +## Hooks: + +- `useErc1155TokenStorage`: Utilizes Jotai and local storage to persist the deployed token's address within the browser's local storage. It updates the value across all components observing it whenever a new deployment occurs. +- `useERC1155Metadata`: Accepts contract and token information (contract address, chainId, tokenId, and ipfsGatewayUrl) and returns a query object from `tanstack-query` with the token metadata information. The hook and the components adhere to the "ERC1155 Metadata JSON Schema" convention for metadata formatting. Learn more [here](https://eips.ethereum.org/EIPS/eip-1155). + +## Components: + +This integration includes read and write/deploy components. The read components solely access the contract information and retrieve the response, whereas the write/deploy components are capable of deploying the contract and performing write actions, requiring a signer and a transaction to be submitted. + +**Read Components:** + +- `ERC1155Name`: Returns the contract's name. +- `ERC1155Symbol`: Returns the contract's symbol. +- `ERC1155TotalSupply`: Returns the total supply of the contract. +- `ERC1155OwnerOf`: Returns the owners of a specific token. +- `ERC1155TokenUriName`: Returns the name of a specific token. +- `ERC1155TokenUriDescription`: Returns the description of a specific token. +- `ERC1155TokenUriImage`: Returns the image of a specific token. +- `Erc1155Read`: Returns a card with aggregate information about the contract and a selected token ID. + +**Write/Deploy Components:** + +- `ERC1155Deploy`: Form for contract deployment. Upon deployment, the contract address is saved in local storage under the key `erc1155-token`. +- `ERC1155DeployTest`: Form for test contract deployment. Upon deployment, the contract address is saved in local storage under the key `erc1155-token`. +- `Erc1155WriteMint`: Form for minting new NFTs. Only the contract owner can mint new NFTs. +- `Erc1155WriteApprove`: Form for approving an address's permission to transfer a token on behalf of the token holder. Only the token holder can perform the approved transaction. +- `Erc1155WriteTransfer`: Form for transferring a token to a different address. Only a token owner or approved address can transfer. + +File Structure + +``` +integrations/erc1155 +├─ artifacts/ +| ├─ core/ +│ | ├─ erc1155-abi.ts +│ | ├─ erc1155-bytecode.ts +| ├─ test/ +│ | ├─ erc1155-abi.ts +│ | ├─ erc1155-bytecode.ts +├─ components/ +│ ├─ erc1155-deploy.tsx +│ ├─ erc1155-name.tsx +│ ├─ erc1155-owner-of.tsx +│ ├─ erc1155-read.tsx +│ ├─ erc1155-set-token-storage.tsx +│ ├─ erc1155-symbol.tsx +│ ├─ erc1155-token-uri-description.tsx +│ ├─ erc1155-token-uri-image.tsx +│ ├─ erc1155-token-uri-name.tsx +│ ├─ erc1155-token-uri.tsx +│ ├─ erc1155-contract-uri.tsx +│ ├─ erc1155-total-supply.tsx +│ ├─ erc1155-write-approve.tsx +│ ├─ erc1155-write-mint.tsx +│ ├─ erc1155-write-transfer.tsx +│ ├─ erc1155-write-batch-transfer.tsx +├─ generated/ +│ ├─ erc1155-wagmi.ts +├─ hooks/ +│ ├─ use-erc1155-metadata.ts +│ ├─ use-erc1155-token-storage.ts +├─ utils/ +│ ├─ types.ts +├─ index.ts +├─ wagmi.config.ts +├─ README.md +``` diff --git a/integrations/safe/artifacts/core/erc1155-abi.ts b/integrations/safe/artifacts/core/erc1155-abi.ts new file mode 100644 index 00000000..45a58cc6 --- /dev/null +++ b/integrations/safe/artifacts/core/erc1155-abi.ts @@ -0,0 +1,753 @@ +/** + * ABI for ERC1155 contract generated using Solidstate Solidity Contract + * https://github.com/solidstate-network/solidstate-solidity + * + * Features: + * - Mintable + * - Enumerable + * - Single Approve + * - Custom TokenId Uri + * + * Access Control: + * - Ownable + */ +export const erc1155ABI = [ + { + inputs: [ + { + internalType: "string", + name: "name_", + type: "string", + }, + { + internalType: "string", + name: "symbol_", + type: "string", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ERC1155Base__ArrayLengthMismatch", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BalanceQueryZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BurnExceedsBalance", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BurnFromZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__ERC1155ReceiverNotImplemented", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__ERC1155ReceiverRejected", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__MintToZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__NotOwnerOrApproved", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__SelfApproval", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__TransferExceedsBalance", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__TransferToZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC165Base__InvalidInterfaceId", + type: "error", + }, + { + inputs: [], + name: "EnumerableSet__IndexOutOfBounds", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "_spender", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "_id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + 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: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + indexed: false, + internalType: "uint256[]", + name: "values", + type: "uint256[]", + }, + ], + name: "TransferBatch", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "TransferSingle", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "string", + name: "value", + type: "string", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "URI", + type: "event", + }, + { + inputs: [], + name: "Fungible", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "NonFungible", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "NonFungible2nd", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "accountsByToken", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_owner", + type: "address", + }, + { + internalType: "address", + name: "_spender", + type: "address", + }, + { + internalType: "uint256", + name: "_id", + type: "uint256", + }, + ], + name: "allowance", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_spender", + type: "address", + }, + { + internalType: "uint256", + name: "_id", + type: "uint256", + }, + { + internalType: "uint256", + name: "_value", + type: "uint256", + }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address[]", + name: "accounts", + type: "address[]", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + ], + name: "balanceOfBatch", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "contractURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "address", + name: "operator", + type: "address", + }, + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "string", + name: "uri_", + type: "string", + }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "mintBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "safeBatchTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + 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: "status", + 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: "address", + name: "account", + type: "address", + }, + ], + name: "tokensByAccount", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "totalHolders", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "uri", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const diff --git a/integrations/safe/artifacts/core/erc1155-bytecode.ts b/integrations/safe/artifacts/core/erc1155-bytecode.ts new file mode 100644 index 00000000..c2b07037 --- /dev/null +++ b/integrations/safe/artifacts/core/erc1155-bytecode.ts @@ -0,0 +1,14 @@ +/** + * Bytecode for ERC1155 contract generated using SolidState Solidity Contract + * https://github.com/solidstate-network/solidstate-solidity + * + * Features: + * - Mintable + * - Enumerable + * - Single Approve + * + * Access Control: + * - Ownable + */ +export const erc1155ByteCode = + "0x60806040523480156200001157600080fd5b5060405162004285380380620042858339818101604052810190620000379190620002e2565b620000576200004b6200008360201b60201c565b6200008b60201b60201c565b8160049081620000689190620005b2565b5080600590816200007a9190620005b2565b50505062000699565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001b8826200016d565b810181811067ffffffffffffffff82111715620001da57620001d96200017e565b5b80604052505050565b6000620001ef6200014f565b9050620001fd8282620001ad565b919050565b600067ffffffffffffffff82111562000220576200021f6200017e565b5b6200022b826200016d565b9050602081019050919050565b60005b83811015620002585780820151818401526020810190506200023b565b60008484015250505050565b60006200027b620002758462000202565b620001e3565b9050828152602081018484840111156200029a576200029962000168565b5b620002a784828562000238565b509392505050565b600082601f830112620002c757620002c662000163565b5b8151620002d984826020860162000264565b91505092915050565b60008060408385031215620002fc57620002fb62000159565b5b600083015167ffffffffffffffff8111156200031d576200031c6200015e565b5b6200032b85828601620002af565b925050602083015167ffffffffffffffff8111156200034f576200034e6200015e565b5b6200035d85828601620002af565b9150509250929050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003ba57607f821691505b602082108103620003d057620003cf62000372565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026200043a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620003fb565b620004468683620003fb565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620004936200048d62000487846200045e565b62000468565b6200045e565b9050919050565b6000819050919050565b620004af8362000472565b620004c7620004be826200049a565b84845462000408565b825550505050565b600090565b620004de620004cf565b620004eb818484620004a4565b505050565b5b81811015620005135762000507600082620004d4565b600181019050620004f1565b5050565b601f82111562000562576200052c81620003d6565b6200053784620003eb565b8101602085101562000547578190505b6200055f6200055685620003eb565b830182620004f0565b50505b505050565b600082821c905092915050565b6000620005876000198460080262000567565b1980831691505092915050565b6000620005a2838362000574565b9150826002028217905092915050565b620005bd8262000367565b67ffffffffffffffff811115620005d957620005d86200017e565b5b620005e58254620003a1565b620005f282828562000517565b600060209050601f8311600181146200062a576000841562000615578287015190505b62000621858262000594565b86555062000691565b601f1984166200063a86620003d6565b60005b8281101562000664578489015182556001820191506020850194506020810190506200063d565b8683101562000684578489015162000680601f89168262000574565b8355505b6001600288020188555050505b505050505050565b613bdc80620006a96000396000f3fe608060405234801561001057600080fd5b50600436106101725760003560e01c8063715018a6116100de578063bd85b03911610097578063e8a3d48511610071578063e8a3d48514610471578063e985e9c51461048f578063f242432a146104bf578063f2fde38b146104db57610172565b8063bd85b03914610405578063c98d8f6214610435578063e10147941461045357610172565b8063715018a61461035757806385bff2e7146103615780638da5cb5b1461039157806395d89b41146103af578063a22cb465146103cd578063bb7fde71146103e957610172565b80631f7fdffa116101305780631f7fdffa146102735780632eb2c2d61461028f578063426a8493146102ab5780634e1273f4146102c7578063598af9e7146102f75780636dcfd8411461032757610172565b8062fdd58e1461017757806301ffc9a7146101a757806306fdde03146101d75780630e89341c146101f557806313ba55df146102255780631f4d0f5114610255575b600080fd5b610191600480360381019061018c9190612873565b6104f7565b60405161019e91906128c2565b60405180910390f35b6101c160048036038101906101bc9190612935565b61050b565b6040516101ce919061297d565b60405180910390f35b6101df61051d565b6040516101ec9190612a28565b60405180910390f35b61020f600480360381019061020a9190612a4a565b6105af565b60405161021c9190612a28565b60405180910390f35b61023f600480360381019061023a9190612a4a565b610654565b60405161024c91906128c2565b60405180910390f35b61025d610666565b60405161026a91906128c2565b60405180910390f35b61028d60048036038101906102889190612c74565b61066b565b005b6102a960048036038101906102a49190612d2f565b610685565b005b6102c560048036038101906102c09190612dfe565b610714565b005b6102e160048036038101906102dc9190612f14565b610811565b6040516102ee919061304a565b60405180910390f35b610311600480360381019061030c919061306c565b6109f3565b60405161031e91906128c2565b60405180910390f35b610341600480360381019061033c9190612a4a565b610a8c565b60405161034e919061317d565b60405180910390f35b61035f610a9e565b005b61037b6004803603810190610376919061319f565b610ab2565b604051610388919061304a565b60405180910390f35b610399610ac4565b6040516103a691906131db565b60405180910390f35b6103b7610aed565b6040516103c49190612a28565b60405180910390f35b6103e760048036038101906103e29190613222565b610b7f565b005b61040360048036038101906103fe9190613303565b610cea565b005b61041f600480360381019061041a9190612a4a565b610d1d565b60405161042c91906128c2565b60405180910390f35b61043d610d2f565b60405161044a91906128c2565b60405180910390f35b61045b610d34565b60405161046891906128c2565b60405180910390f35b610479610d39565b6040516104869190612a28565b60405180910390f35b6104a960048036038101906104a49190613386565b610dcb565b6040516104b6919061297d565b60405180910390f35b6104d960048036038101906104d491906133c6565b610e68565b005b6104f560048036038101906104f0919061319f565b610ef7565b005b60006105038383610f7a565b905092915050565b600061051682611043565b9050919050565b60606004805461052c9061348c565b80601f01602080910402602001604051908101604052809291908181526020018280546105589061348c565b80156105a55780601f1061057a576101008083540402835291602001916105a5565b820191906000526020600020905b81548152906001019060200180831161058857829003601f168201915b5050505050905090565b60606002600083815260200190815260200160002080546105cf9061348c565b80601f01602080910402602001604051908101604052809291908181526020018280546105fb9061348c565b80156106485780601f1061061d57610100808354040283529160200191610648565b820191906000526020600020905b81548152906001019060200180831161062b57829003601f168201915b50505050509050919050565b600061065f826110b4565b9050919050565b600381565b6106736110e1565b61067f8484848461115f565b50505050565b3373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16141580156106c857506106c68533610dcb565b155b156106ff576040517f25dfda0000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61070d338686868686611358565b5050505050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002081905550818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a78460405161080491906128c2565b60405180910390a4505050565b6060815183511461084e576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061085861137c565b60000190506000845167ffffffffffffffff81111561087a57610879612a7c565b5b6040519080825280602002602001820160405280156108a85781602001602082028036833780820191505090505b50905060005b85518110156109e757600073ffffffffffffffffffffffffffffffffffffffff168682815181106108e2576108e16134bd565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610937576040517fdb5d879700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600086838151811061094d5761094c6134bd565b5b602002602001015181526020019081526020016000206000878381518110610978576109776134bd565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548282815181106109ce576109cd6134bd565b5b60200260200101818152505080806001019150506108ae565b50809250505092915050565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000205490509392505050565b6060610a97826113a9565b9050919050565b610aa66110e1565b610ab060006114ab565b565b6060610abd8261156f565b9050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060058054610afc9061348c565b80601f0160208091040260200160405190810160405280929190818152602001828054610b289061348c565b8015610b755780601f10610b4a57610100808354040283529160200191610b75565b820191906000526020600020905b815481529060010190602001808311610b5857829003601f168201915b5050505050905090565b8173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1603610be4576040517ff661526600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80610bed61137c565b60010160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610cde919061297d565b60405180910390a35050565b610cf26110e1565b610d0d8484846040518060200160405280600081525061166f565b610d1783826117e9565b50505050565b6000610d288261180e565b9050919050565b600181565b600281565b606060038054610d489061348c565b80601f0160208091040260200160405190810160405280929190818152602001828054610d749061348c565b8015610dc15780601f10610d9657610100808354040283529160200191610dc1565b820191906000526020600020905b815481529060010190602001808311610da457829003601f168201915b5050505050905090565b6000610dd561137c565b60010160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b3373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614158015610eab5750610ea98533610dcb565b155b15610ee2576040517f25dfda0000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ef0338686868686611834565b5050505050565b610eff6110e1565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f6e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f659061355e565b60405180910390fd5b610f77816114ab565b50565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610fe1576040517fdb5d879700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610fe961137c565b600001600083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600061104d611858565b6000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060009054906101000a900460ff169050919050565b60006110da6110c1611885565b60010160008481526020019081526020016000206118b2565b9050919050565b6110e96118c7565b73ffffffffffffffffffffffffffffffffffffffff16611107610ac4565b73ffffffffffffffffffffffffffffffffffffffff161461115d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611154906135ca565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036111c5576040517f0391df7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151835114611200576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61120f336000868686866118cf565b600061121961137c565b600001905060005b84518110156112d15783818151811061123d5761123c6134bd565b5b602002602001015182600087848151811061125b5761125a6134bd565b5b6020026020010151815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546112bd9190613619565b925050819055508080600101915050611221565b508473ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb878760405161134992919061364d565b60405180910390a45050505050565b6113668686868686866118e5565b611374868686868686611bcf565b505050505050565b6000807f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b90508091505090565b606060006113b5611885565b6001016000848152602001908152602001600020905060006113d6826118b2565b67ffffffffffffffff8111156113ef576113ee612a7c565b5b60405190808252806020026020018201604052801561141d5781602001602082028036833780820191505090505b50905060005b61142c836118b2565b8110156114a0576114468184611d9490919063ffffffff16565b828281518110611459576114586134bd565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050611423565b508092505050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6060600061157b611885565b60020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060006115c882611dae565b67ffffffffffffffff8111156115e1576115e0612a7c565b5b60405190808252806020026020018201604052801561160f5781602001602082028036833780820191505090505b50905060005b61161e83611dae565b811015611664576116388184611dc390919063ffffffff16565b82828151811061164b5761164a6134bd565b5b6020026020010181815250508080600101915050611615565b508092505050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036116d5576040517f0391df7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6116f4336000866116e587611ddd565b6116ee87611ddd565b866118cf565b816116fd61137c565b600001600085815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461175d9190613619565b925050819055508373ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6286866040516117db929190613684565b60405180910390a450505050565b806002600084815260200190815260200160002090816118099190613859565b505050565b6000611818611885565b6000016000838152602001908152602001600020549050919050565b611842868686868686611e57565b6118508686868686866120bc565b505050505050565b6000807ffc606c433378e3a7e0a6a531deac289b66caa1b4aa8554fd4ab2c6f1570f92d890508091505090565b6000807fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ea90508091505090565b60006118c082600001612281565b9050919050565b600033905090565b6118dd868686868686612292565b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361194b576040517ff5cadad500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151835114611986576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6119948686868686866118cf565b600061199e61137c565b600001905060005b8451811015611b475760008582815181106119c4576119c36134bd565b5b6020026020010151905060008583815181106119e3576119e26134bd565b5b60200260200101519050600084600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080821115611a7b576040517f8cd635d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81810385600085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508380600101945050508084600084815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611b399190613619565b9250508190555050506119a6565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051611bbe92919061364d565b60405180910390a450505050505050565b611bee8473ffffffffffffffffffffffffffffffffffffffff16612543565b15611d8c578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b8152600401611c34959493929190613980565b6020604051808303816000875af1925050508015611c7057506040513d601f19601f82011682018060405250810190611c6d91906139fd565b60015b611d0c57611c7c613a37565b806308c379a003611cd85750611c90613a59565b80611c9b5750611cda565b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ccf9190612a28565b60405180910390fd5b505b6040517f380147a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614611d8a576040517f3744db2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b505050505050565b6000611da38360000183612556565b60001c905092915050565b6000611dbc82600001612281565b9050919050565b6000611dd28360000183612556565b60001c905092915050565b60606000600167ffffffffffffffff811115611dfc57611dfb612a7c565b5b604051908082528060200260200182016040528015611e2a5781602001602082028036833780820191505090505b5090508281600081518110611e4257611e416134bd565b5b60200260200101818152505080915050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603611ebd576040517ff5cadad500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611edb868686611ecc87611ddd565b611ed587611ddd565b866118cf565b6000611ee561137c565b6000019050600081600086815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080841115611f78576040517f8cd635d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83810382600087815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550508281600086815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461202e9190613619565b925050819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6287876040516120ab929190613684565b60405180910390a450505050505050565b6120db8473ffffffffffffffffffffffffffffffffffffffff16612543565b15612279578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401612121959493929190613ae9565b6020604051808303816000875af192505050801561215d57506040513d601f19601f8201168201806040525081019061215a91906139fd565b60015b6121f957612169613a37565b806308c379a0036121c5575061217d613a59565b8061218857506121c7565b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121bc9190612a28565b60405180910390fd5b505b6040517f380147a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612277576040517f3744db2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b505050505050565b600081600001805490509050919050565b6122a08686868686866125c1565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161461253b5760006122dd611885565b9050600081600101905060008260020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008360020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060005b8751811015612535576000878281518110612392576123916134bd565b5b6020026020010151905060008111156125275760008983815181106123ba576123b96134bd565b5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168c73ffffffffffffffffffffffffffffffffffffffff16036124295781876000016000838152602001908152602001600020600082825461241d9190613619565b92505081905550612474565b816124348d83610f7a565b036124735761245d8c8760008481526020019081526020016000206125c990919063ffffffff16565b5061247181866125f990919063ffffffff16565b505b5b600073ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff16036124d9578187600001600083815260200190815260200160002060008282546124cd9190613b43565b92505081905550612525565b60006124e58c83610f7a565b036125245761250e8b87600084815260200190815260200160002061261390919063ffffffff16565b50612522818561264390919063ffffffff16565b505b5b505b818060010192505050612374565b50505050505b505050505050565b600080823b905060008111915050919050565b600082600001805490508210612598576040517fe637bf3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260000182815481106125ae576125ad6134bd565b5b9060005260206000200154905092915050565b505050505050565b60006125f1836000018373ffffffffffffffffffffffffffffffffffffffff1660001b61265d565b905092915050565b600061260b836000018360001b61265d565b905092915050565b600061263b836000018373ffffffffffffffffffffffffffffffffffffffff1660001b612741565b905092915050565b6000612655836000018360001b612741565b905092915050565b6000808360010160008481526020019081526020016000205490506000811461273a576000846000016001866000018054905003815481106126a2576126a16134bd565b5b90600052602060002001549050808560000160018403815481106126c9576126c86134bd565b5b90600052602060002001819055508185600101600083815260200190815260200160002081905550508360000180548061270657612705613b77565b5b6001900381819060005260206000200160009055905583600101600084815260200190815260200160002060009055600191505b5092915050565b600061274d83836127a8565b6127a25782600001829080600181540180825580915050600190039060005260206000200160009091909190915055826000018054905083600101600084815260200190815260200160002081905550600190505b92915050565b600080836001016000848152602001908152602001600020541415905092915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061280a826127df565b9050919050565b61281a816127ff565b811461282557600080fd5b50565b60008135905061283781612811565b92915050565b6000819050919050565b6128508161283d565b811461285b57600080fd5b50565b60008135905061286d81612847565b92915050565b6000806040838503121561288a576128896127d5565b5b600061289885828601612828565b92505060206128a98582860161285e565b9150509250929050565b6128bc8161283d565b82525050565b60006020820190506128d760008301846128b3565b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b612912816128dd565b811461291d57600080fd5b50565b60008135905061292f81612909565b92915050565b60006020828403121561294b5761294a6127d5565b5b600061295984828501612920565b91505092915050565b60008115159050919050565b61297781612962565b82525050565b6000602082019050612992600083018461296e565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156129d25780820151818401526020810190506129b7565b60008484015250505050565b6000601f19601f8301169050919050565b60006129fa82612998565b612a0481856129a3565b9350612a148185602086016129b4565b612a1d816129de565b840191505092915050565b60006020820190508181036000830152612a4281846129ef565b905092915050565b600060208284031215612a6057612a5f6127d5565b5b6000612a6e8482850161285e565b91505092915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b612ab4826129de565b810181811067ffffffffffffffff82111715612ad357612ad2612a7c565b5b80604052505050565b6000612ae66127cb565b9050612af28282612aab565b919050565b600067ffffffffffffffff821115612b1257612b11612a7c565b5b602082029050602081019050919050565b600080fd5b6000612b3b612b3684612af7565b612adc565b90508083825260208201905060208402830185811115612b5e57612b5d612b23565b5b835b81811015612b875780612b73888261285e565b845260208401935050602081019050612b60565b5050509392505050565b600082601f830112612ba657612ba5612a77565b5b8135612bb6848260208601612b28565b91505092915050565b600080fd5b600067ffffffffffffffff821115612bdf57612bde612a7c565b5b612be8826129de565b9050602081019050919050565b82818337600083830152505050565b6000612c17612c1284612bc4565b612adc565b905082815260208101848484011115612c3357612c32612bbf565b5b612c3e848285612bf5565b509392505050565b600082601f830112612c5b57612c5a612a77565b5b8135612c6b848260208601612c04565b91505092915050565b60008060008060808587031215612c8e57612c8d6127d5565b5b6000612c9c87828801612828565b945050602085013567ffffffffffffffff811115612cbd57612cbc6127da565b5b612cc987828801612b91565b935050604085013567ffffffffffffffff811115612cea57612ce96127da565b5b612cf687828801612b91565b925050606085013567ffffffffffffffff811115612d1757612d166127da565b5b612d2387828801612c46565b91505092959194509250565b600080600080600060a08688031215612d4b57612d4a6127d5565b5b6000612d5988828901612828565b9550506020612d6a88828901612828565b945050604086013567ffffffffffffffff811115612d8b57612d8a6127da565b5b612d9788828901612b91565b935050606086013567ffffffffffffffff811115612db857612db76127da565b5b612dc488828901612b91565b925050608086013567ffffffffffffffff811115612de557612de46127da565b5b612df188828901612c46565b9150509295509295909350565b600080600060608486031215612e1757612e166127d5565b5b6000612e2586828701612828565b9350506020612e368682870161285e565b9250506040612e478682870161285e565b9150509250925092565b600067ffffffffffffffff821115612e6c57612e6b612a7c565b5b602082029050602081019050919050565b6000612e90612e8b84612e51565b612adc565b90508083825260208201905060208402830185811115612eb357612eb2612b23565b5b835b81811015612edc5780612ec88882612828565b845260208401935050602081019050612eb5565b5050509392505050565b600082601f830112612efb57612efa612a77565b5b8135612f0b848260208601612e7d565b91505092915050565b60008060408385031215612f2b57612f2a6127d5565b5b600083013567ffffffffffffffff811115612f4957612f486127da565b5b612f5585828601612ee6565b925050602083013567ffffffffffffffff811115612f7657612f756127da565b5b612f8285828601612b91565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b612fc18161283d565b82525050565b6000612fd38383612fb8565b60208301905092915050565b6000602082019050919050565b6000612ff782612f8c565b6130018185612f97565b935061300c83612fa8565b8060005b8381101561303d5781516130248882612fc7565b975061302f83612fdf565b925050600181019050613010565b5085935050505092915050565b600060208201905081810360008301526130648184612fec565b905092915050565b600080600060608486031215613085576130846127d5565b5b600061309386828701612828565b93505060206130a486828701612828565b92505060406130b58682870161285e565b9150509250925092565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6130f4816127ff565b82525050565b600061310683836130eb565b60208301905092915050565b6000602082019050919050565b600061312a826130bf565b61313481856130ca565b935061313f836130db565b8060005b8381101561317057815161315788826130fa565b975061316283613112565b925050600181019050613143565b5085935050505092915050565b60006020820190508181036000830152613197818461311f565b905092915050565b6000602082840312156131b5576131b46127d5565b5b60006131c384828501612828565b91505092915050565b6131d5816127ff565b82525050565b60006020820190506131f060008301846131cc565b92915050565b6131ff81612962565b811461320a57600080fd5b50565b60008135905061321c816131f6565b92915050565b60008060408385031215613239576132386127d5565b5b600061324785828601612828565b92505060206132588582860161320d565b9150509250929050565b600067ffffffffffffffff82111561327d5761327c612a7c565b5b613286826129de565b9050602081019050919050565b60006132a66132a184613262565b612adc565b9050828152602081018484840111156132c2576132c1612bbf565b5b6132cd848285612bf5565b509392505050565b600082601f8301126132ea576132e9612a77565b5b81356132fa848260208601613293565b91505092915050565b6000806000806080858703121561331d5761331c6127d5565b5b600061332b87828801612828565b945050602061333c8782880161285e565b935050604061334d8782880161285e565b925050606085013567ffffffffffffffff81111561336e5761336d6127da565b5b61337a878288016132d5565b91505092959194509250565b6000806040838503121561339d5761339c6127d5565b5b60006133ab85828601612828565b92505060206133bc85828601612828565b9150509250929050565b600080600080600060a086880312156133e2576133e16127d5565b5b60006133f088828901612828565b955050602061340188828901612828565b94505060406134128882890161285e565b93505060606134238882890161285e565b925050608086013567ffffffffffffffff811115613444576134436127da565b5b61345088828901612c46565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806134a457607f821691505b6020821081036134b7576134b661345d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b60006135486026836129a3565b9150613553826134ec565b604082019050919050565b600060208201905081810360008301526135778161353b565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b60006135b46020836129a3565b91506135bf8261357e565b602082019050919050565b600060208201905081810360008301526135e3816135a7565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006136248261283d565b915061362f8361283d565b9250828201905080821115613647576136466135ea565b5b92915050565b600060408201905081810360008301526136678185612fec565b9050818103602083015261367b8184612fec565b90509392505050565b600060408201905061369960008301856128b3565b6136a660208301846128b3565b9392505050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b60006008830261370f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826136d2565b61371986836136d2565b95508019841693508086168417925050509392505050565b6000819050919050565b600061375661375161374c8461283d565b613731565b61283d565b9050919050565b6000819050919050565b6137708361373b565b61378461377c8261375d565b8484546136df565b825550505050565b600090565b61379961378c565b6137a4818484613767565b505050565b5b818110156137c8576137bd600082613791565b6001810190506137aa565b5050565b601f82111561380d576137de816136ad565b6137e7846136c2565b810160208510156137f6578190505b61380a613802856136c2565b8301826137a9565b50505b505050565b600082821c905092915050565b600061383060001984600802613812565b1980831691505092915050565b6000613849838361381f565b9150826002028217905092915050565b61386282612998565b67ffffffffffffffff81111561387b5761387a612a7c565b5b613885825461348c565b6138908282856137cc565b600060209050601f8311600181146138c357600084156138b1578287015190505b6138bb858261383d565b865550613923565b601f1984166138d1866136ad565b60005b828110156138f9578489015182556001820191506020850194506020810190506138d4565b868310156139165784890151613912601f89168261381f565b8355505b6001600288020188555050505b505050505050565b600081519050919050565b600082825260208201905092915050565b60006139528261392b565b61395c8185613936565b935061396c8185602086016129b4565b613975816129de565b840191505092915050565b600060a08201905061399560008301886131cc565b6139a260208301876131cc565b81810360408301526139b48186612fec565b905081810360608301526139c88185612fec565b905081810360808301526139dc8184613947565b90509695505050505050565b6000815190506139f781612909565b92915050565b600060208284031215613a1357613a126127d5565b5b6000613a21848285016139e8565b91505092915050565b60008160e01c9050919050565b600060033d1115613a565760046000803e613a53600051613a2a565b90505b90565b600060443d10613ae657613a6b6127cb565b60043d036004823e80513d602482011167ffffffffffffffff82111715613a93575050613ae6565b808201805167ffffffffffffffff811115613ab15750505050613ae6565b80602083010160043d038501811115613ace575050505050613ae6565b613add82602001850186612aab565b82955050505050505b90565b600060a082019050613afe60008301886131cc565b613b0b60208301876131cc565b613b1860408301866128b3565b613b2560608301856128b3565b8181036080830152613b378184613947565b90509695505050505050565b6000613b4e8261283d565b9150613b598361283d565b9250828203905081811115613b7157613b706135ea565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea2646970667358221220be827254403910d63025b0c127a53468f0ffa1e764187eb1bb90856133e3718f64736f6c63430008120033" diff --git a/integrations/safe/artifacts/test/erc1155-abi.ts b/integrations/safe/artifacts/test/erc1155-abi.ts new file mode 100644 index 00000000..9415ae31 --- /dev/null +++ b/integrations/safe/artifacts/test/erc1155-abi.ts @@ -0,0 +1,742 @@ +/** + * Test ABI for ERC1155 contract generated using Solidstate Solidity Contract + * https://github.com/solidstate-network/solidstate-solidity + * + * Features: + * - Mintable + * - Enumerable + * - Single Approve + * - No Contructor Params + * + * Access Control: + * - Ownable + */ +export const erc1155ABI = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ERC1155Base__ArrayLengthMismatch", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BalanceQueryZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BurnExceedsBalance", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__BurnFromZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__ERC1155ReceiverNotImplemented", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__ERC1155ReceiverRejected", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__MintToZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__NotOwnerOrApproved", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__SelfApproval", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__TransferExceedsBalance", + type: "error", + }, + { + inputs: [], + name: "ERC1155Base__TransferToZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ERC165Base__InvalidInterfaceId", + type: "error", + }, + { + inputs: [], + name: "EnumerableSet__IndexOutOfBounds", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "_spender", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "_id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + 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: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + indexed: false, + internalType: "uint256[]", + name: "values", + type: "uint256[]", + }, + ], + name: "TransferBatch", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "TransferSingle", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "string", + name: "value", + type: "string", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "URI", + type: "event", + }, + { + inputs: [], + name: "Fungible", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "NonFungible", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "NonFungible2nd", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "accountsByToken", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_owner", + type: "address", + }, + { + internalType: "address", + name: "_spender", + type: "address", + }, + { + internalType: "uint256", + name: "_id", + type: "uint256", + }, + ], + name: "allowance", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_spender", + type: "address", + }, + { + internalType: "uint256", + name: "_id", + type: "uint256", + }, + { + internalType: "uint256", + name: "_value", + type: "uint256", + }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address[]", + name: "accounts", + type: "address[]", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + ], + name: "balanceOfBatch", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "contractURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "address", + name: "operator", + type: "address", + }, + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "string", + name: "uri_", + type: "string", + }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "mintBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "safeBatchTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + 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: "status", + 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: "address", + name: "account", + type: "address", + }, + ], + name: "tokensByAccount", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "totalHolders", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "uri", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const diff --git a/integrations/safe/artifacts/test/erc1155-bytecode.ts b/integrations/safe/artifacts/test/erc1155-bytecode.ts new file mode 100644 index 00000000..2d74f4f3 --- /dev/null +++ b/integrations/safe/artifacts/test/erc1155-bytecode.ts @@ -0,0 +1,15 @@ +/** + * Test Bytecode for ERC1155 contract generated using SolidState Solidity Contract + * https://github.com/solidstate-network/solidstate-solidity + * + * Features: + * - Mintable + * - Enumerable + * - Single Approve + * - No Contructor Params + * + * Access Control: + * - Ownable + */ +export const erc1155ByteCode = + "0x60806040523480156200001157600080fd5b506200003262000026620001e360201b60201c565b620001eb60201b60201c565b60405180606001604052806036815260200162004cd360369139600390816200005c919062000e11565b506040518060400160405280600f81526020017f506f6e792047616d65204974656d73000000000000000000000000000000000081525060049081620000a3919062000e11565b506040518060400160405280600581526020017f504974656d00000000000000000000000000000000000000000000000000000081525060059081620000ea919062000e11565b506200010f3360018060405180602001604052806000815250620002af60201b60201c565b6200013b60016040518060600160405280603a815260200162004c5f603a91396200045360201b60201c565b62000160336002600560405180602001604052806000815250620002af60201b60201c565b6200018c60026040518060600160405280603a815260200162004c25603a91396200045360201b60201c565b620001b1336003606460405180602001604052806000815250620002af60201b60201c565b620001dd60036040518060600160405280603a815260200162004c99603a91396200045360201b60201c565b62001039565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160362000316576040517f0391df7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6200034d336000866200032f876200047a60201b60201c565b62000340876200047a60201b60201c565b86620004fb60201b60201c565b81620003636200051e60201b62000f7a1760201c565b600001600085815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620003c5919062000f27565b925050819055508373ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6286866040516200044592919062000f73565b60405180910390a450505050565b8060026000848152602001908152602001600020908162000475919062000e11565b505050565b60606000600167ffffffffffffffff8111156200049c576200049b62000ba2565b5b604051908082528060200260200182016040528015620004cb5781602001602082028036833780820191505090505b5090508281600081518110620004e657620004e562000fa0565b5b60200260200101818152505080915050919050565b620005168686868686866200054b60201b62000fa71760201c565b505050505050565b6000807f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b90508091505090565b620005668686868686866200085660201b620012581760201c565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16146200084e576000620005b16200085e60201b620012601760201c565b9050600081600101905060008260020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008360020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060005b8751811015620008485760008782815181106200066a576200066962000fa0565b5b6020026020010151905060008111156200083957600089838151811062000696576200069562000fa0565b5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168c73ffffffffffffffffffffffffffffffffffffffff16036200070957818760000160008381526020019081526020016000206000828254620006fc919062000f27565b925050819055506200076b565b816200071c8d836200088b60201b60201c565b036200076a576200074d8c8760008481526020019081526020016000206200096260201b6200128d1790919060201c565b506200076881866200099a60201b620012bd1790919060201c565b505b5b600073ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff1603620007d457818760000160008381526020019081526020016000206000828254620007c7919062000fcf565b9250508190555062000837565b6000620007e88c836200088b60201b60201c565b036200083657620008198b876000848152602001908152602001600020620009bc60201b620012d71790919060201c565b50620008348185620009f460201b620013071790919060201c565b505b5b505b81806001019250505062000648565b50505050505b505050505050565b505050505050565b6000807fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ea90508091505090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603620008f3576040517fdb5d879700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b620009086200051e60201b62000f7a1760201c565b600001600083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600062000992836000018373ffffffffffffffffffffffffffffffffffffffff1660001b62000a1660201b60201c565b905092915050565b6000620009b4836000018360001b62000a1660201b60201c565b905092915050565b6000620009ec836000018373ffffffffffffffffffffffffffffffffffffffff1660001b62000b0460201b60201c565b905092915050565b600062000a0e836000018360001b62000b0460201b60201c565b905092915050565b6000808360010160008481526020019081526020016000205490506000811462000afd5760008460000160018660000180549050038154811062000a5f5762000a5e62000fa0565b5b906000526020600020015490508085600001600184038154811062000a895762000a8862000fa0565b5b90600052602060002001819055508185600101600083815260200190815260200160002081905550508360000180548062000ac95762000ac86200100a565b5b6001900381819060005260206000200160009055905583600101600084815260200190815260200160002060009055600191505b5092915050565b600062000b18838362000b7460201b60201c565b62000b6e5782600001829080600181540180825580915050600190039060005260206000200160009091909190915055826000018054905083600101600084815260200190815260200160002081905550600190505b92915050565b600080836001016000848152602001908152602001600020541415905092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168062000c1957607f821691505b60208210810362000c2f5762000c2e62000bd1565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b60006008830262000c997fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000c5a565b62000ca5868362000c5a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000cf262000cec62000ce68462000cbd565b62000cc7565b62000cbd565b9050919050565b6000819050919050565b62000d0e8362000cd1565b62000d2662000d1d8262000cf9565b84845462000c67565b825550505050565b600090565b62000d3d62000d2e565b62000d4a81848462000d03565b505050565b5b8181101562000d725762000d6660008262000d33565b60018101905062000d50565b5050565b601f82111562000dc15762000d8b8162000c35565b62000d968462000c4a565b8101602085101562000da6578190505b62000dbe62000db58562000c4a565b83018262000d4f565b50505b505050565b600082821c905092915050565b600062000de66000198460080262000dc6565b1980831691505092915050565b600062000e01838362000dd3565b9150826002028217905092915050565b62000e1c8262000b97565b67ffffffffffffffff81111562000e385762000e3762000ba2565b5b62000e44825462000c00565b62000e5182828562000d76565b600060209050601f83116001811462000e89576000841562000e74578287015190505b62000e80858262000df3565b86555062000ef0565b601f19841662000e998662000c35565b60005b8281101562000ec35784890151825560018201915060208501945060208101905062000e9c565b8683101562000ee3578489015162000edf601f89168262000dd3565b8355505b6001600288020188555050505b505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000f348262000cbd565b915062000f418362000cbd565b925082820190508082111562000f5c5762000f5b62000ef8565b5b92915050565b62000f6d8162000cbd565b82525050565b600060408201905062000f8a600083018562000f62565b62000f99602083018462000f62565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600062000fdc8262000cbd565b915062000fe98362000cbd565b925082820390508181111562001004576200100362000ef8565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b613bdc80620010496000396000f3fe608060405234801561001057600080fd5b50600436106101725760003560e01c8063715018a6116100de578063bd85b03911610097578063e8a3d48511610071578063e8a3d48514610471578063e985e9c51461048f578063f242432a146104bf578063f2fde38b146104db57610172565b8063bd85b03914610405578063c98d8f6214610435578063e10147941461045357610172565b8063715018a61461035757806385bff2e7146103615780638da5cb5b1461039157806395d89b41146103af578063a22cb465146103cd578063bb7fde71146103e957610172565b80631f7fdffa116101305780631f7fdffa146102735780632eb2c2d61461028f578063426a8493146102ab5780634e1273f4146102c7578063598af9e7146102f75780636dcfd8411461032757610172565b8062fdd58e1461017757806301ffc9a7146101a757806306fdde03146101d75780630e89341c146101f557806313ba55df146102255780631f4d0f5114610255575b600080fd5b610191600480360381019061018c9190612873565b6104f7565b60405161019e91906128c2565b60405180910390f35b6101c160048036038101906101bc9190612935565b61050b565b6040516101ce919061297d565b60405180910390f35b6101df61051d565b6040516101ec9190612a28565b60405180910390f35b61020f600480360381019061020a9190612a4a565b6105af565b60405161021c9190612a28565b60405180910390f35b61023f600480360381019061023a9190612a4a565b610654565b60405161024c91906128c2565b60405180910390f35b61025d610666565b60405161026a91906128c2565b60405180910390f35b61028d60048036038101906102889190612c74565b61066b565b005b6102a960048036038101906102a49190612d2f565b610685565b005b6102c560048036038101906102c09190612dfe565b610714565b005b6102e160048036038101906102dc9190612f14565b610811565b6040516102ee919061304a565b60405180910390f35b610311600480360381019061030c919061306c565b6109f3565b60405161031e91906128c2565b60405180910390f35b610341600480360381019061033c9190612a4a565b610a8c565b60405161034e919061317d565b60405180910390f35b61035f610a9e565b005b61037b6004803603810190610376919061319f565b610ab2565b604051610388919061304a565b60405180910390f35b610399610ac4565b6040516103a691906131db565b60405180910390f35b6103b7610aed565b6040516103c49190612a28565b60405180910390f35b6103e760048036038101906103e29190613222565b610b7f565b005b61040360048036038101906103fe9190613303565b610cea565b005b61041f600480360381019061041a9190612a4a565b610d1d565b60405161042c91906128c2565b60405180910390f35b61043d610d2f565b60405161044a91906128c2565b60405180910390f35b61045b610d34565b60405161046891906128c2565b60405180910390f35b610479610d39565b6040516104869190612a28565b60405180910390f35b6104a960048036038101906104a49190613386565b610dcb565b6040516104b6919061297d565b60405180910390f35b6104d960048036038101906104d491906133c6565b610e68565b005b6104f560048036038101906104f0919061319f565b610ef7565b005b60006105038383611321565b905092915050565b6000610516826113ea565b9050919050565b60606004805461052c9061348c565b80601f01602080910402602001604051908101604052809291908181526020018280546105589061348c565b80156105a55780601f1061057a576101008083540402835291602001916105a5565b820191906000526020600020905b81548152906001019060200180831161058857829003601f168201915b5050505050905090565b60606002600083815260200190815260200160002080546105cf9061348c565b80601f01602080910402602001604051908101604052809291908181526020018280546105fb9061348c565b80156106485780601f1061061d57610100808354040283529160200191610648565b820191906000526020600020905b81548152906001019060200180831161062b57829003601f168201915b50505050509050919050565b600061065f8261145b565b9050919050565b600381565b610673611488565b61067f84848484611506565b50505050565b3373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16141580156106c857506106c68533610dcb565b155b156106ff576040517f25dfda0000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61070d3386868686866116ff565b5050505050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002081905550818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a78460405161080491906128c2565b60405180910390a4505050565b6060815183511461084e576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610858610f7a565b60000190506000845167ffffffffffffffff81111561087a57610879612a7c565b5b6040519080825280602002602001820160405280156108a85781602001602082028036833780820191505090505b50905060005b85518110156109e757600073ffffffffffffffffffffffffffffffffffffffff168682815181106108e2576108e16134bd565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610937576040517fdb5d879700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600086838151811061094d5761094c6134bd565b5b602002602001015181526020019081526020016000206000878381518110610978576109776134bd565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548282815181106109ce576109cd6134bd565b5b60200260200101818152505080806001019150506108ae565b50809250505092915050565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000205490509392505050565b6060610a9782611723565b9050919050565b610aa6611488565b610ab06000611825565b565b6060610abd826118e9565b9050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060058054610afc9061348c565b80601f0160208091040260200160405190810160405280929190818152602001828054610b289061348c565b8015610b755780601f10610b4a57610100808354040283529160200191610b75565b820191906000526020600020905b815481529060010190602001808311610b5857829003601f168201915b5050505050905090565b8173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1603610be4576040517ff661526600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80610bed610f7a565b60010160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610cde919061297d565b60405180910390a35050565b610cf2611488565b610d0d848484604051806020016040528060008152506119e9565b610d178382611b63565b50505050565b6000610d2882611b88565b9050919050565b600181565b600281565b606060038054610d489061348c565b80601f0160208091040260200160405190810160405280929190818152602001828054610d749061348c565b8015610dc15780601f10610d9657610100808354040283529160200191610dc1565b820191906000526020600020905b815481529060010190602001808311610da457829003601f168201915b5050505050905090565b6000610dd5610f7a565b60010160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b3373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614158015610eab5750610ea98533610dcb565b155b15610ee2576040517f25dfda0000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ef0338686868686611bae565b5050505050565b610eff611488565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f6e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f659061355e565b60405180910390fd5b610f7781611825565b50565b6000807f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b90508091505090565b610fb5868686868686611258565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614611250576000610ff2611260565b9050600081600101905060008260020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008360020160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060005b875181101561124a5760008782815181106110a7576110a66134bd565b5b60200260200101519050600081111561123c5760008983815181106110cf576110ce6134bd565b5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168c73ffffffffffffffffffffffffffffffffffffffff160361113e5781876000016000838152602001908152602001600020600082825461113291906135ad565b92505081905550611189565b816111498d83611321565b03611188576111728c87600084815260200190815260200160002061128d90919063ffffffff16565b5061118681866112bd90919063ffffffff16565b505b5b600073ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff16036111ee578187600001600083815260200190815260200160002060008282546111e291906135e1565b9250508190555061123a565b60006111fa8c83611321565b03611239576112238b8760008481526020019081526020016000206112d790919063ffffffff16565b50611237818561130790919063ffffffff16565b505b5b505b818060010192505050611089565b50505050505b505050505050565b505050505050565b6000807fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ea90508091505090565b60006112b5836000018373ffffffffffffffffffffffffffffffffffffffff1660001b611bd2565b905092915050565b60006112cf836000018360001b611bd2565b905092915050565b60006112ff836000018373ffffffffffffffffffffffffffffffffffffffff1660001b611cb6565b905092915050565b6000611319836000018360001b611cb6565b905092915050565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603611388576040517fdb5d879700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611390610f7a565b600001600083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60006113f4611d1d565b6000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060009054906101000a900460ff169050919050565b6000611481611468611260565b6001016000848152602001908152602001600020611d4a565b9050919050565b611490611d5f565b73ffffffffffffffffffffffffffffffffffffffff166114ae610ac4565b73ffffffffffffffffffffffffffffffffffffffff1614611504576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fb90613661565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361156c576040517f0391df7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81518351146115a7576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6115b633600086868686611d67565b60006115c0610f7a565b600001905060005b8451811015611678578381815181106115e4576115e36134bd565b5b6020026020010151826000878481518110611602576116016134bd565b5b6020026020010151815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461166491906135ad565b9250508190555080806001019150506115c8565b508473ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516116f0929190613681565b60405180910390a45050505050565b61170d868686868686611d7d565b61171b868686868686612067565b505050505050565b6060600061172f611260565b60010160008481526020019081526020016000209050600061175082611d4a565b67ffffffffffffffff81111561176957611768612a7c565b5b6040519080825280602002602001820160405280156117975781602001602082028036833780820191505090505b50905060005b6117a683611d4a565b81101561181a576117c0818461222c90919063ffffffff16565b8282815181106117d3576117d26134bd565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050808060010191505061179d565b508092505050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b606060006118f5611260565b60020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600061194282612246565b67ffffffffffffffff81111561195b5761195a612a7c565b5b6040519080825280602002602001820160405280156119895781602001602082028036833780820191505090505b50905060005b61199883612246565b8110156119de576119b2818461225b90919063ffffffff16565b8282815181106119c5576119c46134bd565b5b602002602001018181525050808060010191505061198f565b508092505050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603611a4f576040517f0391df7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611a6e33600086611a5f87612275565b611a6887612275565b86611d67565b81611a77610f7a565b600001600085815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611ad791906135ad565b925050819055508373ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628686604051611b559291906136b8565b60405180910390a450505050565b80600260008481526020019081526020016000209081611b83919061388d565b505050565b6000611b92611260565b6000016000838152602001908152602001600020549050919050565b611bbc8686868686866122ef565b611bca868686868686612554565b505050505050565b60008083600101600084815260200190815260200160002054905060008114611caf57600084600001600186600001805490500381548110611c1757611c166134bd565b5b9060005260206000200154905080856000016001840381548110611c3e57611c3d6134bd565b5b906000526020600020018190555081856001016000838152602001908152602001600020819055505083600001805480611c7b57611c7a61395f565b5b6001900381819060005260206000200160009055905583600101600084815260200190815260200160002060009055600191505b5092915050565b6000611cc28383612719565b611d175782600001829080600181540180825580915050600190039060005260206000200160009091909190915055826000018054905083600101600084815260200190815260200160002081905550600190505b92915050565b6000807ffc606c433378e3a7e0a6a531deac289b66caa1b4aa8554fd4ab2c6f1570f92d890508091505090565b6000611d588260000161273c565b9050919050565b600033905090565b611d75868686868686610fa7565b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603611de3576040517ff5cadad500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151835114611e1e576040517f7cfc16da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611e2c868686868686611d67565b6000611e36610f7a565b600001905060005b8451811015611fdf576000858281518110611e5c57611e5b6134bd565b5b602002602001015190506000858381518110611e7b57611e7a6134bd565b5b60200260200101519050600084600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080821115611f13576040517f8cd635d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81810385600085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508380600101945050508084600084815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611fd191906135ad565b925050819055505050611e3e565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051612056929190613681565b60405180910390a450505050505050565b6120868473ffffffffffffffffffffffffffffffffffffffff1661274d565b15612224578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016120cc9594939291906139e3565b6020604051808303816000875af192505050801561210857506040513d601f19601f820116820180604052508101906121059190613a60565b60015b6121a457612114613a9a565b806308c379a0036121705750612128613abc565b806121335750612172565b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121679190612a28565b60405180910390fd5b505b6040517f380147a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612222576040517f3744db2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b505050505050565b600061223b8360000183612760565b60001c905092915050565b60006122548260000161273c565b9050919050565b600061226a8360000183612760565b60001c905092915050565b60606000600167ffffffffffffffff81111561229457612293612a7c565b5b6040519080825280602002602001820160405280156122c25781602001602082028036833780820191505090505b50905082816000815181106122da576122d96134bd565b5b60200260200101818152505080915050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603612355576040517ff5cadad500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61237386868661236487612275565b61236d87612275565b86611d67565b600061237d610f7a565b6000019050600081600086815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080841115612410576040517f8cd635d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83810382600087815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550508281600086815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546124c691906135ad565b925050819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6287876040516125439291906136b8565b60405180910390a450505050505050565b6125738473ffffffffffffffffffffffffffffffffffffffff1661274d565b15612711578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016125b9959493929190613b4c565b6020604051808303816000875af19250505080156125f557506040513d601f19601f820116820180604052508101906125f29190613a60565b60015b61269157612601613a9a565b806308c379a00361265d5750612615613abc565b80612620575061265f565b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016126549190612a28565b60405180910390fd5b505b6040517f380147a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461270f576040517f3744db2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b505050505050565b600080836001016000848152602001908152602001600020541415905092915050565b600081600001805490509050919050565b600080823b905060008111915050919050565b6000826000018054905082106127a2576040517fe637bf3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260000182815481106127b8576127b76134bd565b5b9060005260206000200154905092915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061280a826127df565b9050919050565b61281a816127ff565b811461282557600080fd5b50565b60008135905061283781612811565b92915050565b6000819050919050565b6128508161283d565b811461285b57600080fd5b50565b60008135905061286d81612847565b92915050565b6000806040838503121561288a576128896127d5565b5b600061289885828601612828565b92505060206128a98582860161285e565b9150509250929050565b6128bc8161283d565b82525050565b60006020820190506128d760008301846128b3565b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b612912816128dd565b811461291d57600080fd5b50565b60008135905061292f81612909565b92915050565b60006020828403121561294b5761294a6127d5565b5b600061295984828501612920565b91505092915050565b60008115159050919050565b61297781612962565b82525050565b6000602082019050612992600083018461296e565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156129d25780820151818401526020810190506129b7565b60008484015250505050565b6000601f19601f8301169050919050565b60006129fa82612998565b612a0481856129a3565b9350612a148185602086016129b4565b612a1d816129de565b840191505092915050565b60006020820190508181036000830152612a4281846129ef565b905092915050565b600060208284031215612a6057612a5f6127d5565b5b6000612a6e8482850161285e565b91505092915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b612ab4826129de565b810181811067ffffffffffffffff82111715612ad357612ad2612a7c565b5b80604052505050565b6000612ae66127cb565b9050612af28282612aab565b919050565b600067ffffffffffffffff821115612b1257612b11612a7c565b5b602082029050602081019050919050565b600080fd5b6000612b3b612b3684612af7565b612adc565b90508083825260208201905060208402830185811115612b5e57612b5d612b23565b5b835b81811015612b875780612b73888261285e565b845260208401935050602081019050612b60565b5050509392505050565b600082601f830112612ba657612ba5612a77565b5b8135612bb6848260208601612b28565b91505092915050565b600080fd5b600067ffffffffffffffff821115612bdf57612bde612a7c565b5b612be8826129de565b9050602081019050919050565b82818337600083830152505050565b6000612c17612c1284612bc4565b612adc565b905082815260208101848484011115612c3357612c32612bbf565b5b612c3e848285612bf5565b509392505050565b600082601f830112612c5b57612c5a612a77565b5b8135612c6b848260208601612c04565b91505092915050565b60008060008060808587031215612c8e57612c8d6127d5565b5b6000612c9c87828801612828565b945050602085013567ffffffffffffffff811115612cbd57612cbc6127da565b5b612cc987828801612b91565b935050604085013567ffffffffffffffff811115612cea57612ce96127da565b5b612cf687828801612b91565b925050606085013567ffffffffffffffff811115612d1757612d166127da565b5b612d2387828801612c46565b91505092959194509250565b600080600080600060a08688031215612d4b57612d4a6127d5565b5b6000612d5988828901612828565b9550506020612d6a88828901612828565b945050604086013567ffffffffffffffff811115612d8b57612d8a6127da565b5b612d9788828901612b91565b935050606086013567ffffffffffffffff811115612db857612db76127da565b5b612dc488828901612b91565b925050608086013567ffffffffffffffff811115612de557612de46127da565b5b612df188828901612c46565b9150509295509295909350565b600080600060608486031215612e1757612e166127d5565b5b6000612e2586828701612828565b9350506020612e368682870161285e565b9250506040612e478682870161285e565b9150509250925092565b600067ffffffffffffffff821115612e6c57612e6b612a7c565b5b602082029050602081019050919050565b6000612e90612e8b84612e51565b612adc565b90508083825260208201905060208402830185811115612eb357612eb2612b23565b5b835b81811015612edc5780612ec88882612828565b845260208401935050602081019050612eb5565b5050509392505050565b600082601f830112612efb57612efa612a77565b5b8135612f0b848260208601612e7d565b91505092915050565b60008060408385031215612f2b57612f2a6127d5565b5b600083013567ffffffffffffffff811115612f4957612f486127da565b5b612f5585828601612ee6565b925050602083013567ffffffffffffffff811115612f7657612f756127da565b5b612f8285828601612b91565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b612fc18161283d565b82525050565b6000612fd38383612fb8565b60208301905092915050565b6000602082019050919050565b6000612ff782612f8c565b6130018185612f97565b935061300c83612fa8565b8060005b8381101561303d5781516130248882612fc7565b975061302f83612fdf565b925050600181019050613010565b5085935050505092915050565b600060208201905081810360008301526130648184612fec565b905092915050565b600080600060608486031215613085576130846127d5565b5b600061309386828701612828565b93505060206130a486828701612828565b92505060406130b58682870161285e565b9150509250925092565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6130f4816127ff565b82525050565b600061310683836130eb565b60208301905092915050565b6000602082019050919050565b600061312a826130bf565b61313481856130ca565b935061313f836130db565b8060005b8381101561317057815161315788826130fa565b975061316283613112565b925050600181019050613143565b5085935050505092915050565b60006020820190508181036000830152613197818461311f565b905092915050565b6000602082840312156131b5576131b46127d5565b5b60006131c384828501612828565b91505092915050565b6131d5816127ff565b82525050565b60006020820190506131f060008301846131cc565b92915050565b6131ff81612962565b811461320a57600080fd5b50565b60008135905061321c816131f6565b92915050565b60008060408385031215613239576132386127d5565b5b600061324785828601612828565b92505060206132588582860161320d565b9150509250929050565b600067ffffffffffffffff82111561327d5761327c612a7c565b5b613286826129de565b9050602081019050919050565b60006132a66132a184613262565b612adc565b9050828152602081018484840111156132c2576132c1612bbf565b5b6132cd848285612bf5565b509392505050565b600082601f8301126132ea576132e9612a77565b5b81356132fa848260208601613293565b91505092915050565b6000806000806080858703121561331d5761331c6127d5565b5b600061332b87828801612828565b945050602061333c8782880161285e565b935050604061334d8782880161285e565b925050606085013567ffffffffffffffff81111561336e5761336d6127da565b5b61337a878288016132d5565b91505092959194509250565b6000806040838503121561339d5761339c6127d5565b5b60006133ab85828601612828565b92505060206133bc85828601612828565b9150509250929050565b600080600080600060a086880312156133e2576133e16127d5565b5b60006133f088828901612828565b955050602061340188828901612828565b94505060406134128882890161285e565b93505060606134238882890161285e565b925050608086013567ffffffffffffffff811115613444576134436127da565b5b61345088828901612c46565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806134a457607f821691505b6020821081036134b7576134b661345d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b60006135486026836129a3565b9150613553826134ec565b604082019050919050565b600060208201905081810360008301526135778161353b565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006135b88261283d565b91506135c38361283d565b92508282019050808211156135db576135da61357e565b5b92915050565b60006135ec8261283d565b91506135f78361283d565b925082820390508181111561360f5761360e61357e565b5b92915050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b600061364b6020836129a3565b915061365682613615565b602082019050919050565b6000602082019050818103600083015261367a8161363e565b9050919050565b6000604082019050818103600083015261369b8185612fec565b905081810360208301526136af8184612fec565b90509392505050565b60006040820190506136cd60008301856128b3565b6136da60208301846128b3565b9392505050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026137437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82613706565b61374d8683613706565b95508019841693508086168417925050509392505050565b6000819050919050565b600061378a6137856137808461283d565b613765565b61283d565b9050919050565b6000819050919050565b6137a48361376f565b6137b86137b082613791565b848454613713565b825550505050565b600090565b6137cd6137c0565b6137d881848461379b565b505050565b5b818110156137fc576137f16000826137c5565b6001810190506137de565b5050565b601f82111561384157613812816136e1565b61381b846136f6565b8101602085101561382a578190505b61383e613836856136f6565b8301826137dd565b50505b505050565b600082821c905092915050565b600061386460001984600802613846565b1980831691505092915050565b600061387d8383613853565b9150826002028217905092915050565b61389682612998565b67ffffffffffffffff8111156138af576138ae612a7c565b5b6138b9825461348c565b6138c4828285613800565b600060209050601f8311600181146138f757600084156138e5578287015190505b6138ef8582613871565b865550613957565b601f198416613905866136e1565b60005b8281101561392d57848901518255600182019150602085019450602081019050613908565b8683101561394a5784890151613946601f891682613853565b8355505b6001600288020188555050505b505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600081519050919050565b600082825260208201905092915050565b60006139b58261398e565b6139bf8185613999565b93506139cf8185602086016129b4565b6139d8816129de565b840191505092915050565b600060a0820190506139f860008301886131cc565b613a0560208301876131cc565b8181036040830152613a178186612fec565b90508181036060830152613a2b8185612fec565b90508181036080830152613a3f81846139aa565b90509695505050505050565b600081519050613a5a81612909565b92915050565b600060208284031215613a7657613a756127d5565b5b6000613a8484828501613a4b565b91505092915050565b60008160e01c9050919050565b600060033d1115613ab95760046000803e613ab6600051613a8d565b90505b90565b600060443d10613b4957613ace6127cb565b60043d036004823e80513d602482011167ffffffffffffffff82111715613af6575050613b49565b808201805167ffffffffffffffff811115613b145750505050613b49565b80602083010160043d038501811115613b31575050505050613b49565b613b4082602001850186612aab565b82955050505050505b90565b600060a082019050613b6160008301886131cc565b613b6e60208301876131cc565b613b7b60408301866128b3565b613b8860608301856128b3565b8181036080830152613b9a81846139aa565b9050969550505050505056fea2646970667358221220bf3e3bd60ab289ae1e95f84f1b8af2c43d2ae3e523a3b3d5549a9dea7d6749da64736f6c63430008120033697066733a2f2f697066732f516d624d32666f587570363934685a71563941396531507a6756384670623641797a38647863396532764e393859697066733a2f2f697066732f516d63474e4d55366d776a5979336f7044504b4a446144574a6d4c41416f315671633552484c77447938746b7377697066733a2f2f697066732f516d5044354347785250724a6f5a4c35756f75737251486964505744554b504a626a576575376379674666757234697066733a2f2f516d586b50394e6a546157354478346d686e597a584366703169554471756276784e696d716f365a586b654b75582f" diff --git a/integrations/safe/components/add-owner-dialog.tsx b/integrations/safe/components/add-owner-dialog.tsx new file mode 100644 index 00000000..acb0272a --- /dev/null +++ b/integrations/safe/components/add-owner-dialog.tsx @@ -0,0 +1,83 @@ +import React, { Dispatch, SetStateAction, useState } from 'react' + +import { useForm } from 'react-hook-form' +import { FaChevronDown, FaPlus } from 'react-icons/fa' + +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from '@/components/ui/dialog' +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' + +import { addOwnerForm } from './manage-safe' + +const AddOwnerDialog = ({ + ownersAmount, + isLoading, + open, + setOpen, + onSubmit, +}: { + ownersAmount: number + isLoading: boolean + open: boolean + setOpen: Dispatch> + onSubmit: (FieldValues: addOwnerForm, newThreshold: number | undefined) => Promise +}) => { + const { register, handleSubmit } = useForm() + const [newThreshold, setNewThreshold] = useState(undefined) + + return ( + + + + + + New Owner + Provide new owner‘s address and set new treshold +
onSubmit(fieldValues, newThreshold))}> +
+ + +
+
+ + +
+ + + + +
+ + {[...Array(ownersAmount + 1).keys()].map((num) => { + return ( + setNewThreshold(num + 1)}> + {num + 1} + + ) + })} + +
+
+ +
+
+
+ ) +} + +export default AddOwnerDialog diff --git a/integrations/safe/components/delete-owner.tsx b/integrations/safe/components/delete-owner.tsx new file mode 100644 index 00000000..95bc9943 --- /dev/null +++ b/integrations/safe/components/delete-owner.tsx @@ -0,0 +1,91 @@ +import React, { Dispatch, SetStateAction, useState } from 'react' + +import { FaChevronDown, FaRegTrashAlt } from 'react-icons/fa' +import { Address } from 'wagmi' + +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from '@/components/ui/dialog' +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' + +const DeleteOwner = ({ + owners, + ownersAmount, + isLoading, + open, + setOpen, + onSubmit, +}: { + owners: string[] + ownersAmount: number + isLoading: boolean + open: boolean + setOpen: Dispatch> + onSubmit: (ownerToDelete: string, newThreshold: number | undefined) => Promise +}) => { + const [newThreshold, setNewThreshold] = useState(undefined) + const [ownerToDelete, setOwnerToDelete] = useState
('') + + const handleDelete = (owner: string) => { + setOwnerToDelete(owner) + setNewThreshold(undefined) + } + + return ( + +
+

Owners

+ {owners.map((owner) => ( +
+
{owner}
+ {ownersAmount <= 1 ? null : ( + + + + )} +
+ ))} +
+ + Delete Owner + Confirm owner delete and set new treshold +
+ + +
+
+ + +
+ + + + +
+ + {[...Array(ownersAmount).keys()] + .filter((num) => num !== 0) + .map((num) => { + return ( + setNewThreshold(num)}> + {num} + + ) + })} + +
+
+ +
+
+ ) +} + +export default DeleteOwner diff --git a/integrations/safe/components/form-deploy-safe.tsx b/integrations/safe/components/form-deploy-safe.tsx new file mode 100644 index 00000000..80f49ff1 --- /dev/null +++ b/integrations/safe/components/form-deploy-safe.tsx @@ -0,0 +1,156 @@ +'use client' + +import { useContext, useState } from 'react' + +import { SafeAccountConfig } from '@safe-global/protocol-kit' +import { useForm } from 'react-hook-form' +import { FaPlus, FaRegTrashAlt } from 'react-icons/fa' +import { Address, useAccount, usePublicClient, useQuery } from 'wagmi' + +import { BlockExplorerLink } from '@/components/blockchain/block-explorer-link' +import { useToast } from '@/lib/hooks/use-toast' + +import { Client } from '../safe-client' +import { SafeContext } from '../safe-provider' + +/** + * Starter component placeholder. Replace with your own component. + */ +interface deploySafeForm { + owners: string[] + threshold: number + safeAccountConfig: SafeAccountConfig +} + +interface owner { + index: number + value: string +} + +export function FormDeploySafe() { + const publicClient = usePublicClient() + const { address } = useAccount() + const [isLoading, setIsLoading] = useState(false) + const safeClient: Client = useContext(SafeContext) as Client + const { register, handleSubmit } = useForm() + const [loadedOwners, setLoadedOwners] = useState([]) + const [safeAddress, setSafeAddress] = useState
('0x') + const { toast, dismiss } = useToast() + + const handleToast = ({ title, description }: { title: string; description: string }) => { + toast({ + title, + description, + }) + + setTimeout(() => { + dismiss() + }, 10000) + } + + const { data: nonce } = useQuery(['wallet-nonce', address, publicClient], { + queryFn: async () => { + if (!publicClient || !address) return + return await publicClient.getTransactionCount({ + address, + }) + }, + enabled: !!address && !!publicClient, + }) + + function onSubmit(FieldValues: deploySafeForm) { + setIsLoading(true) + if (safeClient.factory != undefined && FieldValues.threshold != undefined && address != undefined) { + const safeAccountConfig: SafeAccountConfig = { + owners: [address, ...loadedOwners.map((owner) => owner.value)], // ['0x
', '0x
', '0x
'] + threshold: FieldValues.threshold, + } + safeClient.factory + .deploySafe({ safeAccountConfig, saltNonce: nonce?.toString() }) + .then(async (safe) => { + const addr: Address = (await safe.getAddress()) as Address + setSafeAddress(addr) + handleToast({ + title: 'New Safe created', + description: 'Click on contract address to explore new Safe.', + }) + }) + .catch((error) => { + console.log(error) + handleToast({ + title: 'An Error Occurred', + description: 'Error when trying to create a new Safe. Try again later.', + }) + }) + setIsLoading(false) + } + // TODO: handle errors in the form on submit + } + + const addBtnClick = (e: React.MouseEvent) => { + e.preventDefault() + setLoadedOwners([...loadedOwners, { index: loadedOwners.length, value: '' }]) + } + + const handleChange = (e: React.ChangeEvent, index: number) => { + const values: owner[] = [...loadedOwners] + values[index].value = e.target.value + setLoadedOwners(values) + } + + const handleDelete = (index: number) => { + const values = loadedOwners.filter((item) => item.index !== index) + const newValues = values.map((val, index) => { + val.index = index + return val + }) + setLoadedOwners([...newValues]) + } + + return ( +
+
+

Set the owner wallets of your Safe Account and how many need to confirm to execute a valid transaction.

+
+ Owners: + + {loadedOwners.length > 0 ? ( + loadedOwners.map((owner) => ( +
+ handleChange(e, owner.index)} + /> + +
+ )) + ) : ( +
+ )} +
+ + + + + + {safeAddress.length > 2 ? ( +
+ Contract Address: + +
+ ) : null} +
+ ) +} diff --git a/integrations/safe/components/form-send-safe-transaction.tsx b/integrations/safe/components/form-send-safe-transaction.tsx new file mode 100644 index 00000000..1aa6c149 --- /dev/null +++ b/integrations/safe/components/form-send-safe-transaction.tsx @@ -0,0 +1,99 @@ +'use client' + +import { useContext, useState } from 'react' + +import Safe from '@safe-global/protocol-kit' +import { SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types' +import { ethers } from 'ethers' +import { useForm } from 'react-hook-form' +import { Address, useAccount } from 'wagmi' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { useToast } from '@/lib/hooks/use-toast' + +import { useConnectedSafe } from '../hooks/use-connect-safe' +import { Client } from '../safe-client' +import { SafeContext } from '../safe-provider' + +/** + * Starter component placeholder. Replace with your own component. + */ +interface sendSafeTransactionForm { + address: Address + amount: string +} + +export function FormSendSafeTransaction() { + const { safeAddress, safeSdk }: { safeAddress: Address; safeSdk: Safe } = useConnectedSafe() + const { address } = useAccount() + const [isLoading, setIsLoading] = useState(false) + const safeClient: Client = useContext(SafeContext) as Client + const { register, handleSubmit } = useForm() + const { toast, dismiss } = useToast() + + const handleToast = ({ title, description }: { title: string; description: string }) => { + toast({ + title, + description, + }) + + setTimeout(() => { + dismiss() + }, 10000) + } + + async function onSubmit(FieldValues: sendSafeTransactionForm) { + setIsLoading(true) + const decimals = 18 + const value = ethers.utils.parseUnits(FieldValues.amount, decimals).toString() + const nonce = await safeClient.service.getNextNonce(safeAddress) + const safeTransactionData: SafeTransactionDataPartial = { + to: FieldValues.address, + data: '0x', + value, + nonce, + } + // Create txn + const safeTransaction = await safeSdk.createTransaction({ safeTransactionData }) + // Get txn hash + const safeTransactionHash = await safeSdk.getTransactionHash(safeTransaction) + // Sign txn + const ownerSignature = await safeSdk.signTransactionHash(safeTransactionHash) + // Propose txn + if (address) { + try { + await safeClient.service.proposeTransaction({ + safeAddress: safeAddress, + safeTransactionData: safeTransaction.data, + safeTxHash: safeTransactionHash, + senderAddress: address.toString(), + senderSignature: ownerSignature.data, + }) + handleToast({ + title: 'Transaction proposal created', + description: `Txn ${safeTransactionHash} created, visit Safe App to execute`, + }) + } catch (error) { + console.log(error) + } + } + setIsLoading(false) + } + + return ( +
+
+

Create send transaction

+ + + + + +
+
+ ) +} diff --git a/integrations/safe/components/manage-safe.tsx b/integrations/safe/components/manage-safe.tsx new file mode 100644 index 00000000..ea9acb29 --- /dev/null +++ b/integrations/safe/components/manage-safe.tsx @@ -0,0 +1,132 @@ +'use client' + +import { useContext, useEffect, useState } from 'react' + +import { SafeInfoResponse } from '@safe-global/api-kit' +import Safe, { AddOwnerTxParams, RemoveOwnerTxParams } from '@safe-global/protocol-kit' +import { Address } from 'wagmi' + +import { BlockExplorerLink } from '@/components/blockchain/block-explorer-link' +import { useToast } from '@/lib/hooks/use-toast' + +import AddOwnerDialog from './add-owner-dialog' +import DeleteOwner from './delete-owner' +import { useConnectedSafe } from '../hooks/use-connect-safe' +import { Client } from '../safe-client' +import { SafeContext } from '../safe-provider' + +export interface addOwnerForm { + newOwner: string +} + +export function ManageSafe() { + const { safeAddress, safeSdk }: { safeAddress: Address; safeSdk: Safe } = useConnectedSafe() + const safeClient: Client = useContext(SafeContext) as Client + const [safeInfo, setSafeInfo] = useState() + const [owners, setOwners] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const { toast, dismiss } = useToast() + const [openDeleteDialog, setOpenDeleteDialog] = useState(false) + const [openAddDialog, setOpenAddDialog] = useState(false) + useEffect(() => { + safeClient && + safeAddress && + safeClient.service + .getSafeInfo(safeAddress) + .then((res) => { + setSafeInfo(res) + setOwners(res.owners) + }) + .catch((error) => console.log(error)) + }, [safeAddress]) + + const handleToast = ({ title, description }: { title: string; description: string }) => { + toast({ + title, + description, + }) + + setTimeout(() => { + dismiss() + }, 10000) + } + + const onSubmitDelete = async (ownerToDelete: string, newThreshold: number | undefined) => { + setIsLoading(true) + const params: RemoveOwnerTxParams = { + ownerAddress: ownerToDelete, + threshold: newThreshold, // Optional. If `newThreshold` is not provided, the current threshold will be decreased by one. + } + // TODO: fix threshold needs to be greater than 0 + const safeTransaction = await safeSdk.createRemoveOwnerTx(params) + const txResponse = await safeSdk.executeTransaction(safeTransaction) + await txResponse.transactionResponse?.wait() + console.log(txResponse) + setIsLoading(false) + handleToast({ + title: 'Transaction created', + description: 'Owner will be removed after txn gets executed', + }) + const safeInfo = await safeClient.service.getSafeInfo(safeAddress) + setSafeInfo(safeInfo) + setOwners(safeInfo.owners) + setOpenDeleteDialog(false) + } + + const onSubmitAddOwner = async (FieldValues: addOwnerForm, newThreshold: number | undefined) => { + setIsLoading(true) + const params: AddOwnerTxParams = { + ownerAddress: FieldValues.newOwner, + threshold: newThreshold, // Optional. If `threshold` is not provided the current threshold will not change. + } + const safeTransaction = await safeSdk.createAddOwnerTx(params) + const txResponse = await safeSdk.executeTransaction(safeTransaction) + await txResponse.transactionResponse?.wait() + console.log(txResponse) + setIsLoading(false) + handleToast({ + title: 'Transaction created', + description: 'Owner will be added after txn gets executed', + }) + const safeInfo = await safeClient.service.getSafeInfo(safeAddress) + setSafeInfo(safeInfo) + setOwners(safeInfo.owners) + setOpenAddDialog(false) + } + + return ( +
+ {!safeSdk ? null : ( + <> +
+

Manage Safe

+
+
+

Address

+ +
+
+

Treshold

{safeInfo?.threshold} +
+
+ + +
+ + )} +
+ ) +} diff --git a/integrations/safe/components/pending-safe-transactions.tsx b/integrations/safe/components/pending-safe-transactions.tsx new file mode 100644 index 00000000..3e3c0a56 --- /dev/null +++ b/integrations/safe/components/pending-safe-transactions.tsx @@ -0,0 +1,73 @@ +'use client' + +import { useContext, useEffect, useState } from 'react' + +import { SafeMultisigTransactionListResponse } from '@safe-global/api-kit' +import Safe from '@safe-global/protocol-kit' +import { Address } from 'wagmi' + +import { useConnectedSafe } from '../hooks/use-connect-safe' +import { Client } from '../safe-client' +import { SafeContext } from '../safe-provider' + +export function PendingSafeTransactions() { + const { safeAddress, safeSdk }: { safeAddress: Address; safeSdk: Safe } = useConnectedSafe() + const safeClient: Client = useContext(SafeContext) as Client + const [pendingTxns, setPendingTxns] = useState() + + // TODO: hear for new pending transactions + + useEffect(() => { + safeClient && + safeAddress && + safeClient.service + .getPendingTransactions(safeAddress) + .then((res) => { + setPendingTxns(res) + }) + .catch((error) => console.log(error)) + }, [safeAddress]) + + const formatDate = (utcDate: string): string => { + const localDate = new Date(utcDate) + return localDate.toLocaleString() + } + + // TODO: Improve pending txn layout + return ( +
+ {!safeSdk ? ( +

Connect Safe to see pending transactions

+ ) : ( + <> +

Pending transactions

+
+ {pendingTxns?.count == 0 ? ( +

No pending transactions in this Safe

+ ) : ( + pendingTxns?.results.map((txn) => ( +
+
+

SafeTxHash

{' '} + {txn.safeTxHash.slice(0, 6) + '...' + txn.safeTxHash.slice(-4)} +
+
+

Nonce

{txn.nonce} +
+
+

Created

{' '} + {formatDate(txn.submissionDate)} +
+
+

Type

{' '} + {txn.data ? 'Contract Call' : 'Send'} +
+
+ )) + )} +
+ + )} +
+ ) +} diff --git a/integrations/safe/components/tx-builder/ChecksumWarning.tsx b/integrations/safe/components/tx-builder/ChecksumWarning.tsx new file mode 100644 index 00000000..82fc3d22 --- /dev/null +++ b/integrations/safe/components/tx-builder/ChecksumWarning.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import styled from 'styled-components' +import { useTransactionLibrary } from '../store' + +const ChecksumWarning = () => { + const { hasChecksumWarning, setHasChecksumWarning } = useTransactionLibrary() + + if (!hasChecksumWarning) { + return null + } + + return ( + + setHasChecksumWarning(false)}> + + This batch contains some changed properties since you saved or downloaded it + + + + ) +} + +const ChecksumWrapper = styled.div` + position: fixed; + width: 100%; + z-index: 10; + background-color: transparent; + height: 70px; +` + +export default ChecksumWarning diff --git a/integrations/safe/components/tx-builder/CreateNewBatchCard.tsx b/integrations/safe/components/tx-builder/CreateNewBatchCard.tsx new file mode 100644 index 00000000..3ffe0e30 --- /dev/null +++ b/integrations/safe/components/tx-builder/CreateNewBatchCard.tsx @@ -0,0 +1,125 @@ +import { useRef } from 'react' +import { ButtonLink, Icon, Text } from '@gnosis.pm/safe-react-components' +import { alpha } from '@material-ui/core' +import Hidden from '@material-ui/core/Hidden' +import styled from 'styled-components' +import { useTheme } from '@material-ui/core/styles' + +import { ReactComponent as CreateNewBatchSVG } from '../assets/add-new-batch.svg' +import useDropZone from '../hooks/useDropZone' +import { useMediaQuery } from '@material-ui/core' + +type CreateNewBatchCardProps = { + onFileSelected: (file: File | null) => void +} + +const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { + const theme = useTheme() + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')) + + const fileRef = useRef(null) + const { isOverDropZone, isAcceptError, dropHandlers } = useDropZone((file: File | null) => { + onFileSelected(file) + }, '.json') + + const handleFileSelected = (event: any) => { + event.preventDefault() + if (event.target.files.length) { + onFileSelected(event.target.files[0]) + event.target.value = '' + } + } + + const handleBrowse = function (event: any) { + event.preventDefault() + fileRef.current?.click() + } + + return ( + + + + + + {isAcceptError ? ( + + The uploaded file is not a valid JSON file + + ) : ( + <> + + Drag and drop a JSON file or + + choose a file + + + )} + + + + ) +} + +export default CreateNewBatchCard + +const Wrapper = styled.div<{ isSmallScreen: boolean }>` + margin-top: ${({ isSmallScreen }) => (isSmallScreen ? '0' : '64px')}; +` + +const StyledDragAndDropFileContainer = styled.div<{ + dragOver: Boolean + fullWidth: boolean + error: Boolean +}>` + box-sizing: border-box; + max-width: ${({ fullWidth }) => (fullWidth ? '100%' : '420px')}; + border: 2px dashed ${({ theme, error }) => (error ? theme.colors.error : '#008c73')}; + border-radius: 8px; + background-color: ${({ theme, error }) => (error ? alpha(theme.colors.error, 0.7) : '#eaf7f4')}; + padding: 24px; + margin: 24px auto 0 auto; + + display: flex; + justify-content: center; + align-items: center; + + ${({ dragOver, error, theme }) => { + if (dragOver) { + return ` + transition: all 0.2s ease-in-out; + transform: scale(1.05); + ` + } + + return ` + border-color: ${error ? theme.colors.error : '#008c73'}; + background-color: ${error ? alpha(theme.colors.error, 0.7) : '#eaf7f4'}; + ` + }} +` + +const StyledText = styled(Text)<{ error?: Boolean }>` + margin-left: 4px; + color: ${({ error }) => (error ? '#FFF' : '#566976')}; +` + +const StyledButtonLink = styled(ButtonLink)` + padding: 0; + text-decoration: none; + + && > p { + font-size: 16px; + } +` diff --git a/integrations/safe/components/tx-builder/EditableLabel.tsx b/integrations/safe/components/tx-builder/EditableLabel.tsx new file mode 100644 index 00000000..c6749468 --- /dev/null +++ b/integrations/safe/components/tx-builder/EditableLabel.tsx @@ -0,0 +1,44 @@ +import styled from 'styled-components' + +export type EditableLabelProps = { + children: React.ReactNode + onEdit: (value: string) => void +} + +const EditableLabel = ({ children, onEdit }: EditableLabelProps) => { + return ( + onEdit(event.target.innerText)} + onKeyPress={(event: any) => + event.key === 'Enter' && event.target.blur() && event.preventDefault() + } + onClick={event => event.stopPropagation()} + > + {children} + + ) +} + +export default EditableLabel + +const EditableComponent = styled.div` + font-family: Averta, 'Roboto', sans-serif; + display: block; + white-space: nowrap; + overflow: hidden; + + padding: 10px; + cursor: text; + border-radius: 8px; + border: 1px solid transparent; + + &:hover { + border-color: #e2e3e3; + } + + &:focus { + outline-color: #008c73; + } +` diff --git a/integrations/safe/components/tx-builder/ErrorAlert.tsx b/integrations/safe/components/tx-builder/ErrorAlert.tsx new file mode 100644 index 00000000..8a86ab46 --- /dev/null +++ b/integrations/safe/components/tx-builder/ErrorAlert.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import styled from 'styled-components' +import { useTransactionLibrary } from '../store' + +const ErrorAlert = () => { + const { errorMessage, setErrorMessage } = useTransactionLibrary() + + if (!errorMessage) { + return null + } + + return ( + + setErrorMessage('')}> + {errorMessage} + + + ) +} + +const ErrorAlertContainer = styled.div` + position: fixed; + width: 100%; + z-index: 10; + background-color: transparent; + height: 70px; +` + +export default ErrorAlert diff --git a/integrations/safe/components/tx-builder/Header.test.tsx b/integrations/safe/components/tx-builder/Header.test.tsx new file mode 100644 index 00000000..25f547ff --- /dev/null +++ b/integrations/safe/components/tx-builder/Header.test.tsx @@ -0,0 +1,35 @@ +import { screen, waitFor } from '@testing-library/react' + +import { render } from '../test-utils' +import Header from './Header' + +// Axios is bundled as ESM module which is not directly compatible with Jest +// https://jestjs.io/docs/ecmascript-modules +jest.mock('axios', () => ({ + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +})) + +describe('
', () => { + it('Renders Header component', async () => { + render(
) + + await waitFor(() => { + expect(screen.getByText('Transaction Builder')).toBeInTheDocument() + }) + }) + + it('Shows Link to Transaction Library in Create Batch pathname', async () => { + render(
) + + await waitFor(() => { + expect(screen.getByText('Transaction Builder')).toBeInTheDocument() + expect( + screen.getByText('Your transaction library', { + exact: false, + }), + ).toBeInTheDocument() + }) + }) +}) diff --git a/integrations/safe/components/tx-builder/Header.tsx b/integrations/safe/components/tx-builder/Header.tsx new file mode 100644 index 00000000..e4cb3682 --- /dev/null +++ b/integrations/safe/components/tx-builder/Header.tsx @@ -0,0 +1,130 @@ +import { FixedIcon, Icon, Text, Title, Tooltip } from '@gnosis.pm/safe-react-components' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import styled from 'styled-components' + +import { + CREATE_BATCH_PATH, + EDIT_BATCH_PATH, + HOME_PATH, + SAVE_BATCH_PATH, + TRANSACTION_LIBRARY_PATH, +} from '../routes/routes' +import { useTransactionLibrary } from '../store' +import ChecksumWarning from './ChecksumWarning' +import ErrorAlert from './ErrorAlert' + +const HELP_ARTICLE_LINK = 'https://help.safe.global/en/articles/40841-transaction-builder' + +const goBackLabel: Record = { + [CREATE_BATCH_PATH]: 'Back to Transaction Creation', + [TRANSACTION_LIBRARY_PATH]: 'Back to Your Transaction Library', + [EDIT_BATCH_PATH]: 'Back to Edit Batch', + [SAVE_BATCH_PATH]: 'Back to Transaction Creation', +} + +type LocationType = { + state: { from: string } | null +} + +const Header = () => { + const { pathname } = useLocation() + + const navigate = useNavigate() + + const goBack = () => navigate(-1) + + const { batches } = useTransactionLibrary() + + const isTransactionCreationPath = pathname === CREATE_BATCH_PATH + const isSaveBatchPath = pathname === SAVE_BATCH_PATH + + const showTitle = isTransactionCreationPath || isSaveBatchPath + const showLinkToLibrary = isTransactionCreationPath || isSaveBatchPath + + const { state } = useLocation() as LocationType + + const previousUrl = state?.from || CREATE_BATCH_PATH + + return ( + <> + + {showTitle ? ( + <> + {/* Transaction Builder Title */} + Transaction Builder + + + + + + + ) : ( + + {/* Go Back link */} + + {goBackLabel[previousUrl]} + + )} + + {showLinkToLibrary && ( + + + {`(${batches.length}) Your transaction library`} + + + + )} + + + + + ) +} + +export default Header + +const HeaderWrapper = styled.header` + position: fixed; + width: 100%; + display: flex; + align-items: center; + border-bottom: 1px solid #e2e3e3; + z-index: 10; + background-color: white; + height: 70px; + padding: 0 40px; + box-sizing: border-box; +` + +const StyledTitle = styled(Title)` + font-size: 20px; + margin: 0 10px 0 0; +` + +const StyledLink = styled(Link)` + display: flex; + align-items: center; + color: #000000; + font-size: 16px; + text-decoration: none; +` + +const StyledLeftLinkLabel = styled(Text)` + margin-left: 8px; +` + +const RigthLinkWrapper = styled.div` + display: flex; + flex-grow: 1; + justify-content: flex-end; +` + +const StyledRightLinkLabel = styled(Text)` + margin-right: 8px; +` diff --git a/integrations/safe/components/tx-builder/QuickTip.tsx b/integrations/safe/components/tx-builder/QuickTip.tsx new file mode 100644 index 00000000..ac1a46c4 --- /dev/null +++ b/integrations/safe/components/tx-builder/QuickTip.tsx @@ -0,0 +1,51 @@ +import { Icon } from '@gnosis.pm/safe-react-components' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import React from 'react' +import styled from 'styled-components' + +type QuickTipProps = { + onClose: () => void +} + +const QuickTip = ({ onClose }: QuickTipProps) => { + return ( + + Quick Tip + You can save your batches in your transaction library{' '} + (local + browser storage) or{' '} + download the + .json file to use them later. + + ) +} + +const StyledAlert = styled(MuiAlert)` + && { + font-family: 'Averta'; + font-size: 14px; + padding: 24px; + background: #eaf7f4; + color: #566976; + border-radius: 8px; + + .MuiAlert-action { + align-items: flex-start; + } + } +` + +const StyledTitle = styled(MuiAlertTitle)` + && { + font-size: 14px; + font-weight: bold; + } +` + +const StyledIcon = styled(Icon)` + position: relative; + top: 3px; +` + +export default QuickTip diff --git a/integrations/safe/components/tx-builder/ShowMoreText.tsx b/integrations/safe/components/tx-builder/ShowMoreText.tsx new file mode 100644 index 00000000..83e150bb --- /dev/null +++ b/integrations/safe/components/tx-builder/ShowMoreText.tsx @@ -0,0 +1,37 @@ +import { useState, SyntheticEvent } from 'react' +import { Link } from '@gnosis.pm/safe-react-components' + +type ShowMoreTextProps = { + children: string + moreLabel?: string + lessLabel?: string + splitIndex?: number +} + +const SHOW_MORE = 'Show more' +const SHOW_LESS = 'Show less' + +export const ShowMoreText = ({ + children, + moreLabel = SHOW_MORE, + lessLabel = SHOW_LESS, + splitIndex = 50, +}: ShowMoreTextProps) => { + const [expanded, setExpanded] = useState(false) + + const handleToggle = (event: SyntheticEvent) => { + event.preventDefault() + setExpanded(!expanded) + } + + if (children.length < splitIndex) { + return {children} + } + + return ( + <> + {expanded ? `${children} ` : `${children.substr(0, splitIndex)} ... `} + {expanded ? lessLabel : moreLabel} + + ) +} diff --git a/integrations/safe/components/tx-builder/TransactionBatchListItem.tsx b/integrations/safe/components/tx-builder/TransactionBatchListItem.tsx new file mode 100644 index 00000000..53785d6c --- /dev/null +++ b/integrations/safe/components/tx-builder/TransactionBatchListItem.tsx @@ -0,0 +1,354 @@ +import { + Accordion, + AccordionSummary, + Dot, + EthHashInfo, + FixedIcon, + Icon, + Text, + Tooltip, +} from '@gnosis.pm/safe-react-components' +import { AccordionDetails, IconButton } from '@material-ui/core' +import { memo, useState } from 'react' +import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd' +import styled from 'styled-components' +import DragIndicatorIcon from '@material-ui/icons/DragIndicator' +import { ProposedTransaction } from '../typings/models' +import TransactionDetails from './TransactionDetails' +import { getTransactionText } from '../utils' + +const UNKNOWN_POSITION_LABEL = '?' +const minArrowSize = '12' + +type TransactionProps = { + transaction: ProposedTransaction + provided: DraggableProvided + snapshot: DraggableStateSnapshot + isLastTransaction: boolean + showTransactionDetails: boolean + index: number + draggableTxIndexDestination: number | undefined + draggableTxIndexOrigin: number | undefined + reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void + networkPrefix: string | undefined + replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void + setTxIndexToEdit: (index: string) => void + openEditTxModal: () => void + removeTransaction?: (index: number) => void + setTxIndexToRemove: (index: string) => void + openDeleteTxModal: () => void +} + +const TransactionBatchListItem = memo( + ({ + transaction, + provided, + snapshot, + isLastTransaction, + showTransactionDetails, + index, + draggableTxIndexDestination, + draggableTxIndexOrigin, + reorderTransactions, + networkPrefix, + replaceTransaction, + setTxIndexToEdit, + openEditTxModal, + removeTransaction, + setTxIndexToRemove, + openDeleteTxModal, + }: TransactionProps) => { + const { description } = transaction + const { to } = description + + const transactionDescription = getTransactionText(description) + + const [isTxExpanded, setTxExpanded] = useState(false) + + const onClickShowTransactionDetails = () => { + if (showTransactionDetails) { + setTxExpanded(isTxExpanded => !isTxExpanded) + } + } + const isThisTxBeingDragging = snapshot.isDragging + + const showArrowAdornment = !isLastTransaction && !isThisTxBeingDragging + + // displayed order can change if the user uses the drag and drop feature + const displayedTxPosition = getDisplayedTxPosition( + index, + isThisTxBeingDragging, + draggableTxIndexDestination, + draggableTxIndexOrigin, + ) + + return ( + + {/* Transacion Position */} + + + {displayedTxPosition} + + {showArrowAdornment && } + + + {/* Transaction Description */} + +
+ + {/* Drag & Drop Indicator */} + {reorderTransactions && ( + + + + )} + + {/* Destination Address label */} + + + {/* Transaction Description label */} + {transactionDescription} + + {/* Transaction Actions */} + + {/* Edit transaction */} + {replaceTransaction && ( + + { + event.stopPropagation() + setTxIndexToEdit(String(index)) + openEditTxModal() + }} + > + + + + )} + + {/* Delete transaction */} + {removeTransaction && ( + + { + event.stopPropagation() + setTxIndexToRemove(String(index)) + openDeleteTxModal() + }} + size="medium" + aria-label="Delete transaction" + > + + + + )} + + {/* Expand transaction details */} + {showTransactionDetails && ( + + { + event.stopPropagation() + onClickShowTransactionDetails() + }} + size="medium" + aria-label="Expand transaction details" + > + + + + )} + +
+ + {/* Transaction details */} + + + +
+
+ ) + }, +) + +const getDisplayedTxPosition = ( + index: number, + isDraggingThisTx: boolean, + draggableTxIndexDestination?: number, + draggableTxIndexOrigin?: number, +): string => { + // we show the correct position in the transaction that is being dragged + if (isDraggingThisTx) { + const isAwayFromDroppableZone = draggableTxIndexDestination === undefined + return isAwayFromDroppableZone + ? UNKNOWN_POSITION_LABEL + : String(draggableTxIndexDestination + 1) + } + + // if a transaction is being dragged, we show the correct position in previous transactions + if (index < Number(draggableTxIndexOrigin)) { + // depending on the current destination we show the correct position + return index >= Number(draggableTxIndexDestination) ? `${index + 2}` : `${index + 1}` + } + + // if a transaction is being dragged, we show the correct position in next transactions + if (index > Number(draggableTxIndexOrigin)) { + // depending on the current destination we show the correct position + return index > Number(draggableTxIndexDestination) ? `${index + 1}` : `${index}` + } + + // otherwise we show the natural position + return `${index + 1}` +} + +const TransactionListItem = styled.li` + display: flex; + margin-bottom: 8px; +` + +// transaction postion dot styles + +const PositionWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 14px 10px 0 0; +` + +const PositionDot = styled(Dot).withConfig({ + shouldForwardProp: (prop, defaultValidatorFn) => defaultValidatorFn(prop), +})<{ isDragging: boolean }>` + height: 24px; + width: 24px; + min-width: 24px; + background-color: ${({ isDragging }) => (isDragging ? '#92c9be' : ' #e2e3e3')}; + transition: background-color 0.5s linear; +` + +const ArrowAdornment = styled.div` + position: relative; + border-left: 1px solid #e2e3e3; + flex-grow: 1; + margin-top: 8px; + + &&::before { + content: ' '; + display: inline-block; + position: absolute; + border-left: 1px solid #e2e3e3; + + height: ${minArrowSize}px; + bottom: -${minArrowSize}px; + left: -1px; + } + + &&::after { + content: ' '; + display: inline-block; + position: absolute; + bottom: -${minArrowSize}px; + left: -4px; + + border-width: 0 1px 1px 0; + border-style: solid; + border-color: #e2e3e3; + padding: 3px; + + transform: rotate(45deg); + } +` + +// transaction description styles + +const StyledAccordion = styled(Accordion).withConfig({ + shouldForwardProp: prop => !['isDragging'].includes(prop), +})<{ isDragging: boolean }>` + flex-grow: 1; + + &.MuiAccordion-root { + margin-bottom: 0; + border-color: ${({ isDragging, expanded }) => (isDragging || expanded ? '#92c9be' : '#e8e7e6')}; + transition: border-color 0.5s linear; + } + + .MuiAccordionSummary-root { + height: 52px; + padding: 0px 8px; + background-color: ${({ isDragging }) => (isDragging ? '#EFFAF8' : '#FFFFFF')}; + + &:hover { + background-color: #ffffff; + } + + .MuiIconButton-root { + padding: 8px; + } + + &.Mui-expanded { + background-color: #effaf8; + border-color: ${({ isDragging, expanded }) => + isDragging || expanded ? '#92c9be' : '#e8e7e6'}; + } + } + + .MuiAccordionSummary-content { + max-width: 100%; + align-items: center; + } +` + +const TransactionActionButton = styled(IconButton)` + height: 32px; + width: 32px; + padding: 0; +` + +const TransactionsDescription = styled(Text)` + flex-grow: 1; + padding-left: 24px; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const DragAndDropIndicatorIcon = styled(DragIndicatorIcon)` + color: #b2bbc0; + margin-right: 4px; +` + +export default TransactionBatchListItem diff --git a/integrations/safe/components/tx-builder/TransactionDetails.tsx b/integrations/safe/components/tx-builder/TransactionDetails.tsx new file mode 100644 index 00000000..5aaeb1c6 --- /dev/null +++ b/integrations/safe/components/tx-builder/TransactionDetails.tsx @@ -0,0 +1,196 @@ +import { ButtonLink, EthHashInfo, Text, Title } from '@gnosis.pm/safe-react-components' +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' + +import useElementHeight from '../hooks/useElementHeight/useElementHeight' +import { ProposedTransaction } from '../typings/models' +import { weiToEther } from '../utils' + +type TransactionDetailsProp = { + transaction: ProposedTransaction +} + +const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { + const { description, raw } = transaction + + const { to, value, data } = raw + const { + contractMethod, + contractFieldsValues, + customTransactionData, + networkPrefix, + nativeCurrencySymbol, + } = description + + const isCustomHexDataTx = !!customTransactionData + const isContractInteractionTx = !!contractMethod && !isCustomHexDataTx + + const isTokenTransferTx = !isCustomHexDataTx && !isContractInteractionTx + + return ( + + + {isTokenTransferTx + ? `Transfer ${weiToEther(value)} ${nativeCurrencySymbol} to:` + : 'Interact with:'} + + + + + + {/* to address */} + + to (address) + + + + {/* value */} + + value: + + {`${weiToEther(value)} ${nativeCurrencySymbol}`} + + {/* data */} + + data: + + {data} + + {isContractInteractionTx && ( + <> + {/* method */} + + method: + + {contractMethod.name} + + {/* method inputs */} + {contractMethod.inputs.map(({ name, type }, index) => { + const inputName = name || index + const inputLabel = `${inputName} (${type})` + const inputValue = contractFieldsValues?.[inputName] + return ( + + {/* input name */} + + {inputLabel} + + {/* input value */} + {inputValue} + + ) + })} + + )} + + + ) +} + +export default TransactionDetails + +const Wrapper = styled.article` + flex-grow: 1; + padding: 0 16px; + user-select: text; +` + +const TxSummaryContainer = styled.div` + display: grid; + grid-template-columns: minmax(100px, 2fr) minmax(100px, 5fr); + gap: 4px; + + margin-top: 16px; +` + +const StyledTxTitle = styled(Title)` + font-size: 16px; + margin: 8px 0; + font-weight: bold; + line-height: initial; +` + +const StyledMethodNameLabel = styled(Text)` + padding-left: 4px; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const LINE_HEIGHT = 22 +const MAX_HEIGHT = 2 * LINE_HEIGHT // 2 lines as max height + +const TxValueLabel = ({ children }: { children: React.ReactNode }) => { + const [showMore, setShowMore] = useState(false) + const [showEllipsis, setShowEllipsis] = useState(false) + + const { height: containerHeight, elementRef } = useElementHeight() + + // we show the Show more/less button if the height is more than 44px (the height of 2 lines) + const showMoreButton = containerHeight && containerHeight > MAX_HEIGHT + + // we show/hide ellipsis at the end of the second line if user clicks on "Show more" + useEffect(() => { + if (showMoreButton && !showMore) { + setShowEllipsis(true) + } + }, [showMoreButton, showMore]) + + return ( +
+ {/* value */} + + {children} + + + {/* show more/less button */} + {showMoreButton && ( + setShowMore(showMore => !showMore)}> + {showMore ? 'Show less' : 'Show more'} + + )} +
+ ) +} + +const StyledTxValueLabel = styled(Text).withConfig({ + shouldForwardProp: prop => !['showMore'].includes(prop) || !['showEllipsis'].includes(prop), +})<{ showMore?: boolean; showEllipsis?: boolean }>` + max-height: ${({ showMore }) => (showMore ? '100%' : `${MAX_HEIGHT + 1}px`)}; + + line-break: anywhere; + overflow: hidden; + word-break: break-all; + text-overflow: ellipsis; + + ${({ showEllipsis, showMore }) => + !showMore && + showEllipsis && + `@supports (-webkit-line-clamp: 2) { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + }`} +` + +const StyledButtonLink = styled(ButtonLink)` + padding: 0; + + && > p { + margin 0; + } + +` diff --git a/integrations/safe/components/tx-builder/TransactionsBatchList.tsx b/integrations/safe/components/tx-builder/TransactionsBatchList.tsx new file mode 100644 index 00000000..f2382b91 --- /dev/null +++ b/integrations/safe/components/tx-builder/TransactionsBatchList.tsx @@ -0,0 +1,441 @@ +import { isValidElement, useMemo, useState } from 'react' +import { Dot, Text, Title, Icon, Tooltip } from '@gnosis.pm/safe-react-components' + +import IconButton from '@material-ui/core/IconButton' +import styled from 'styled-components' +import { + DragDropContext, + Droppable, + DroppableProvided, + DragStart, + DragUpdate, + DropResult, + Draggable, + DraggableProvided, + DraggableStateSnapshot, +} from 'react-beautiful-dnd' +import { ProposedTransaction } from '../typings/models' +import useModal from '../hooks/useModal/useModal' +import DeleteTransactionModal from './modals/DeleteTransactionModal' +import DeleteBatchModal from './modals/DeleteBatchModal' +import SaveBatchModal from './modals/SaveBatchModal' +import EditTransactionModal from './modals/EditTransactionModal' +import { useNetwork, useTransactionLibrary } from '../store' +import Item from './TransactionBatchListItem' +import VirtualizedList from './VirtualizedList' +import { getTransactionText } from '../utils' +import { EditableLabelProps } from './EditableLabel' + +type TransactionsBatchListProps = { + transactions: ProposedTransaction[] + showTransactionDetails: boolean + showBatchHeader: boolean + // batch title has multiple types because there are files passing it as a string + // or 2 types of components: + // 1: apps/tx-builder/src/pages/EditTransactionLibrary.tsx + // 2: apps/tx-builder/src/pages/CreateTransactions.tsx + batchTitle?: + | string + | React.ReactElement + | React.ReactElement<{ filename: string }> + removeTransaction?: (index: number) => void + saveBatch?: (name: string, transactions: ProposedTransaction[]) => void + downloadBatch?: (name: string, transactions: ProposedTransaction[]) => void + removeAllTransactions?: () => void + replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void + reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void +} + +const TRANSACTION_LIST_DROPPABLE_ID = 'Transaction_List' +const DROP_EVENT = 'DROP' + +const TransactionsBatchList = ({ + transactions, + reorderTransactions, + removeTransaction, + removeAllTransactions, + replaceTransaction, + saveBatch, + downloadBatch, + showTransactionDetails, + showBatchHeader, + batchTitle, +}: TransactionsBatchListProps) => { + // we need those states to display the correct position in each tx during the drag & drop + const { batch } = useTransactionLibrary() + const [draggableTxIndexOrigin, setDraggableTxIndexOrigin] = useState() + const [draggableTxIndexDestination, setDraggableTxIndexDestination] = useState() + + const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork() + + const onDragStart = ({ source }: DragStart) => { + setDraggableTxIndexOrigin(source.index) + setDraggableTxIndexDestination(source.index) + } + + const onDragUpdate = ({ source, destination }: DragUpdate) => { + setDraggableTxIndexOrigin(source.index) + setDraggableTxIndexDestination(destination?.index) + } + + // we only perform the reorder if its present + const onDragEnd = ({ reason, source, destination }: DropResult) => { + const sourceIndex = source.index + const destinationIndex = destination?.index + + const isDropEvent = reason === DROP_EVENT // because user can cancel the drag & drop + const hasTxPositionChanged = sourceIndex !== destinationIndex && destinationIndex !== undefined + + const shouldPerformTxReorder = isDropEvent && hasTxPositionChanged + + if (shouldPerformTxReorder) { + reorderTransactions?.(sourceIndex, destinationIndex) + } + + setDraggableTxIndexOrigin(undefined) + setDraggableTxIndexDestination(undefined) + } + + // 5 modals needed: save batch modal, edit transaction modal, delete batch modal, delete transaction modal, download batch modal + const { + open: showDeleteBatchModal, + openModal: openClearTransactions, + closeModal: closeDeleteBatchModal, + } = useModal() + const { + open: showSaveBatchModal, + openModal: openSaveBatchModal, + closeModal: closeSaveBatchModal, + } = useModal() + const { + open: showDeleteTxModal, + openModal: openDeleteTxModal, + closeModal: closeDeleteTxModal, + } = useModal() + const { + open: showEditTxModal, + openModal: openEditTxModal, + closeModal: closeEditTxModal, + } = useModal() + + const [txIndexToRemove, setTxIndexToRemove] = useState() + const [txIndexToEdit, setTxIndexToEdit] = useState() + + const fileName = useMemo(() => { + if (isValidElement(batchTitle)) { + if ('filename' in batchTitle.props) { + return batchTitle.props.filename + } else if (batchTitle.props.children) { + return batchTitle.props.children.toString() + } + + return 'Untitled' + } + + return batchTitle || 'Untitled' + }, [batchTitle]) + + return ( + <> + + {/* Transactions Batch Header */} + {showBatchHeader && ( + + {/* Transactions Batch Counter */} + + + {transactions.length} + + + + {/* Transactions Batch Title */} + {batchTitle && ( + + {batchTitle} + + )} + + {/* Transactions Batch Actions */} + {saveBatch && ( + + + + + + )} + {downloadBatch && ( + + downloadBatch(fileName, transactions)}> + + + + )} + + {removeAllTransactions && ( + + + + + + )} + + )} + + {/* Standard Transactions List */} + {transactions.length <= 20 && ( + + + {(provided: DroppableProvided) => ( + + {transactions.map((transaction: any, index: number) => ( + + {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => ( + + )} + + ))} + {provided.placeholder} + + )} + + + )} + + {/* Virtualized Transaction List */} + {transactions.length > 20 && ( + + ( + + )} + > + {(provided: DroppableProvided) => ( + + ( + + {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => ( + + )} + + )} + /> + {transactions.length <= 20 && provided.placeholder} + + )} + + + )} + + + {/* Edit transaction modal */} + {showEditTxModal && ( + { + closeEditTxModal() + replaceTransaction?.(updatedTransaction, Number(txIndexToEdit)) + }} + onDeleteTx={() => { + closeEditTxModal() + removeTransaction?.(Number(txIndexToEdit)) + }} + onClose={closeEditTxModal} + networkPrefix={networkPrefix} + getAddressFromDomain={getAddressFromDomain} + nativeCurrencySymbol={nativeCurrencySymbol} + /> + )} + + {/* Delete batch modal */} + {showDeleteBatchModal && removeAllTransactions && ( + { + closeDeleteBatchModal() + removeAllTransactions() + }} + onClose={closeDeleteBatchModal} + /> + )} + + {/* Delete a transaction modal */} + {showDeleteTxModal && ( + { + closeDeleteTxModal() + removeTransaction?.(Number(txIndexToRemove)) + }} + onClose={closeDeleteTxModal} + /> + )} + + {/* Save batch modal */} + {showSaveBatchModal && ( + { + closeSaveBatchModal() + saveBatch?.(name, transactions) + }} + onClose={closeSaveBatchModal} + /> + )} + + ) +} + +export default TransactionsBatchList + +// tx positions can change during drag & drop + +const TransactionsBatchWrapper = styled.section` + width: 100%; + user-select: none; +` + +// batch header styles + +const TransactionHeader = styled.header` + margin-top: 24px; + display: flex; + align-items: center; +` + +const TransactionCounterDot = styled(Dot)` + height: 24px; + width: 24px; + min-width: 24px; + background-color: #566976; +` + +const TransactionsTitle = styled(Title)` + flex-grow: 1; + margin-left: 14px; + min-width: 0; + + font-size: 16px; + line-height: normal; + display: flex; + align-items: center; +` + +const StyledHeaderIconButton = styled(IconButton)` + &.MuiIconButton-root { + border-radius: 4px; + background-color: white; + margin-left: 8px; + } +` + +// transactions list styles + +const TransactionList = styled.ol` + list-style: none; + padding: 0; +` diff --git a/integrations/safe/components/tx-builder/VirtualizedList.tsx b/integrations/safe/components/tx-builder/VirtualizedList.tsx new file mode 100644 index 00000000..c0da55b3 --- /dev/null +++ b/integrations/safe/components/tx-builder/VirtualizedList.tsx @@ -0,0 +1,56 @@ +import { memo, useEffect, useState } from 'react' +import { Virtuoso } from 'react-virtuoso' +import styled from 'styled-components' + +type VirtualizedListProps = { + innerRef: any + items: T[] + renderItem: (item: T, index: number) => React.ReactNode +} + +const VirtualizedList = ({ + innerRef, + items, + renderItem, +}: VirtualizedListProps) => { + return ( + renderItem(item, index)} + components={{ + Item: HeightPreservingItem, + }} + totalCount={items.length} + overscan={100} + /> + ) +} + +const HeightPreservingItem: React.FC = memo(({ children, ...props }: any) => { + const [size, setSize] = useState(0) + const knownSize = props['data-known-size'] + + useEffect(() => { + setSize(prevSize => { + return knownSize === 0 ? prevSize : knownSize + }) + }, [knownSize]) + + return ( + + {children} + + ) +}) + +const HeightPreservingContainer = styled.div<{ size: number }>` + --child-height: ${props => `${props.size}px`}; + &:empty { + min-height: calc(var(--child-height)); + box-sizing: border-box; + } +` + +export default VirtualizedList diff --git a/integrations/safe/components/tx-builder/forms/AddNewTransactionForm.tsx b/integrations/safe/components/tx-builder/forms/AddNewTransactionForm.tsx new file mode 100644 index 00000000..e5df7872 --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/AddNewTransactionForm.tsx @@ -0,0 +1,75 @@ +import { Title, Button } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' + +import { ContractInterface } from '../../typings/models' +import { isValidAddress } from '../../utils' +import SolidityForm, { + CONTRACT_METHOD_INDEX_FIELD_NAME, + SolidityFormValuesTypes, + TO_ADDRESS_FIELD_NAME, + parseFormToProposedTransaction, +} from './SolidityForm' +import { useTransactions, useNetwork } from '../../store' + +type AddNewTransactionFormProps = { + contract: ContractInterface | null + to: string + showHexEncodedData: boolean +} + +const AddNewTransactionForm = ({ + contract, + to, + showHexEncodedData, +}: AddNewTransactionFormProps) => { + const initialFormValues = { + [TO_ADDRESS_FIELD_NAME]: isValidAddress(to) ? to : '', + [CONTRACT_METHOD_INDEX_FIELD_NAME]: '0', + } + + const { addTransaction } = useTransactions() + const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork() + + const onSubmit = (values: SolidityFormValuesTypes) => { + const proposedTransaction = parseFormToProposedTransaction( + values, + contract, + nativeCurrencySymbol, + networkPrefix, + ) + + addTransaction(proposedTransaction) + } + + return ( + <> + Transaction information + + + + {/* Add transaction btn */} + + + + + ) +} + +export default AddNewTransactionForm + +const ButtonContainer = styled.div` + display: flex; + justify-content: space-between; + margin-top: 15px; +` diff --git a/integrations/safe/components/tx-builder/forms/SolidityForm.test.tsx b/integrations/safe/components/tx-builder/forms/SolidityForm.test.tsx new file mode 100644 index 00000000..95a98fb5 --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/SolidityForm.test.tsx @@ -0,0 +1,182 @@ +import { screen, waitFor, queryByText, getByText, fireEvent } from '@testing-library/react' + +import { render } from '../../test-utils' +import { ContractInterface } from '../../typings/models' +import SolidityForm, { + CONTRACT_METHOD_INDEX_FIELD_NAME, + TO_ADDRESS_FIELD_NAME, +} from './SolidityForm' + +// Axios is bundled as ESM module which is not directly compatible with Jest +// https://jestjs.io/docs/ecmascript-modules +jest.mock('axios', () => ({ + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +})) + +const testAddressMethod = { + inputs: [{ internalType: 'address', name: 'newValue', type: 'address' }], + name: 'testAddressValue', + payable: false, +} + +const testBooleanMethod = { + inputs: [{ internalType: 'bool', name: 'newValue', type: 'bool' }], + name: 'testBooleanValue', + payable: false, +} + +const initialValues = { + [TO_ADDRESS_FIELD_NAME]: '0x680cde08860141F9D223cE4E620B10Cd6741037E', + [CONTRACT_METHOD_INDEX_FIELD_NAME]: '0', +} + +const testContract: ContractInterface = { + methods: [testAddressMethod, testBooleanMethod], +} + +describe('', () => { + it('Renders SolidityForm component', async () => { + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('test-form')).toBeInTheDocument() + }) + }) + + it('Show correct field contract params', async () => { + render( + + + , + ) + + // testAddressMethod is selected by default + await waitFor(() => { + expect(screen.queryByText('testAddressValue')).toBeInTheDocument() + expect(screen.queryByText('testBooleanValue')).not.toBeInTheDocument() + }) + + // selects a different contract method + await waitFor(() => { + const contractMethodSelectorNode = screen.getByTestId('contract-method-selector') + + // opens the contract method selector + fireEvent.mouseDown(contractMethodSelectorNode) + + // shows all the available methods in the selector options + const selectorModal = screen.getByTestId('menu-contractMethodIndex') + expect(selectorModal).toBeInTheDocument() + expect(queryByText(selectorModal, 'testAddressValue')).toBeInTheDocument() + expect(queryByText(selectorModal, 'testBooleanValue')).toBeInTheDocument() + + // we select a different contract method + fireEvent.click(getByText(selectorModal, 'testBooleanValue')) + }) + + // now testBooleanMethod is selected by default + await waitFor(() => { + expect(screen.queryByText('testBooleanValue')).toBeInTheDocument() + expect(screen.queryByText('testAddressValue')).not.toBeInTheDocument() + }) + }) + + // see https://github.com/safe-global/safe-react-apps/issues/450 + it('Avoid collisions between parameters with the same name and different types when changing contract methods', async () => { + render( + + + , + ) + + // testAddressMethod is selected by default + await waitFor(() => { + expect(screen.queryByText('testAddressValue')).toBeInTheDocument() + expect(screen.queryByText('testBooleanValue')).not.toBeInTheDocument() + + // no value by default + expect(screen.getByTestId('contract-field-newValue')).toHaveValue('') + }) + + // we update the address field value + await waitFor(() => { + fireEvent.change(screen.getByTestId('contract-field-newValue'), { + target: { value: '0x680cde08860141F9D223cE4E620B10Cd6741037E' }, + }) + }) + + // we update the address field value + await waitFor(() => { + expect(screen.getByTestId('contract-field-newValue')).toHaveValue( + '0x680cde08860141F9D223cE4E620B10Cd6741037E', + ) + }) + + // selects a different contract method + await waitFor(() => { + const contractMethodSelectorNode = screen.getByTestId('contract-method-selector') + + // opens the contract method selector + fireEvent.mouseDown(contractMethodSelectorNode) + + // we select the boolean contract method + const selectorModal = screen.getByTestId('menu-contractMethodIndex') + fireEvent.click(getByText(selectorModal, 'testBooleanValue')) + }) + + // the issue is not present (true value as default for booleans) + await waitFor(() => { + expect(screen.getByTestId('contract-field-newValue-input')).toHaveValue('true') + }) + + // address value again if we select testAddressMethod again + await waitFor(() => { + const contractMethodSelectorNode = screen.getByTestId('contract-method-selector') + + // opens the contract method selector + fireEvent.mouseDown(contractMethodSelectorNode) + + // we select the boolean contract method + const selectorModal = screen.getByTestId('menu-contractMethodIndex') + fireEvent.click(getByText(selectorModal, 'testAddressValue')) + }) + + await waitFor(() => { + expect(screen.getByTestId('contract-field-newValue')).toHaveValue( + '0x680cde08860141F9D223cE4E620B10Cd6741037E', + ) + }) + }) +}) diff --git a/integrations/safe/components/tx-builder/forms/SolidityForm.tsx b/integrations/safe/components/tx-builder/forms/SolidityForm.tsx new file mode 100644 index 00000000..e3c68234 --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/SolidityForm.tsx @@ -0,0 +1,262 @@ +import { useEffect } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { DevTool } from '@hookform/devtools' +import { toChecksumAddress, toWei } from 'web3-utils' + +import { + ADDRESS_FIELD_TYPE, + CONTRACT_METHOD_FIELD_TYPE, + CUSTOM_TRANSACTION_DATA_FIELD_TYPE, + NATIVE_AMOUNT_FIELD_TYPE, +} from './fields/fields' +import Field from './fields/Field' +import { encodeToHexData, getInputTypeHelper } from '../../utils' +import { ContractInterface, ProposedTransaction } from '../../typings/models' + +export const TO_ADDRESS_FIELD_NAME = 'toAddress' +export const NATIVE_VALUE_FIELD_NAME = 'nativeAmount' +export const CONTRACT_METHOD_INDEX_FIELD_NAME = 'contractMethodIndex' +export const CONTRACT_VALUES_FIELD_NAME = 'contractFieldsValues' +export const CUSTOM_TRANSACTION_DATA_FIELD_NAME = 'customTransactionData' + +type SolidityFormPropsTypes = { + id: string + networkPrefix: undefined | string + getAddressFromDomain: (name: string) => Promise + nativeCurrencySymbol: undefined | string + contract: ContractInterface | null + onSubmit: SubmitHandler + initialValues?: Partial + showHexToggler?: boolean + children: React.ReactNode + showHexEncodedData: boolean +} + +export type SolidityInitialFormValuesTypes = { + [TO_ADDRESS_FIELD_NAME]: string + [CONTRACT_METHOD_INDEX_FIELD_NAME]: string +} + +export type SolidityFormValuesTypes = { + [TO_ADDRESS_FIELD_NAME]: string + [NATIVE_VALUE_FIELD_NAME]: string + [CONTRACT_METHOD_INDEX_FIELD_NAME]: string + [CONTRACT_VALUES_FIELD_NAME]: Record> + [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: string +} + +export const parseFormToProposedTransaction = ( + values: SolidityFormValuesTypes, + contract: ContractInterface | null, + nativeCurrencySymbol: string | undefined, + networkPrefix: string | undefined, +): ProposedTransaction => { + const contractMethodIndex = values[CONTRACT_METHOD_INDEX_FIELD_NAME] + const toAddress = values[TO_ADDRESS_FIELD_NAME] + const tokenValue = values[NATIVE_VALUE_FIELD_NAME] + const contractFieldsValues = values[CONTRACT_VALUES_FIELD_NAME] + const methodValues = contractFieldsValues?.[`method-${contractMethodIndex}`] + const customTransactionData = values[CUSTOM_TRANSACTION_DATA_FIELD_NAME] + + const contractMethod = contract?.methods[Number(contractMethodIndex)] + + const data = customTransactionData || encodeToHexData(contractMethod, methodValues) || '0x' + const to = toChecksumAddress(toAddress) + const value = toWei(tokenValue || '0') + + return { + id: new Date().getTime(), + contractInterface: contract, + description: { + to, + value, + customTransactionData, + contractMethod, + contractFieldsValues: methodValues, + contractMethodIndex, + nativeCurrencySymbol, + networkPrefix, + }, + raw: { to, value, data }, + } +} + +const isProdEnv = process.env.NODE_ENV === 'production' + +const SolidityForm = ({ + id, + onSubmit, + getAddressFromDomain, + initialValues, + nativeCurrencySymbol, + networkPrefix, + contract, + children, + showHexEncodedData, +}: SolidityFormPropsTypes) => { + const { + handleSubmit, + control, + setValue, + watch, + getValues, + reset, + formState: { isSubmitSuccessful, dirtyFields }, + } = useForm({ + defaultValues: initialValues, + mode: 'onTouched', // This option allows you to configure the validation strategy before the user submits the form + }) + + const toAddress = watch(TO_ADDRESS_FIELD_NAME) + const contractMethodIndex = watch(CONTRACT_METHOD_INDEX_FIELD_NAME) + const nativeValue = watch(NATIVE_VALUE_FIELD_NAME) + const customTransactionData = watch(CUSTOM_TRANSACTION_DATA_FIELD_NAME) + const contractMethod = contract?.methods[Number(contractMethodIndex)] + + const contractFields = contractMethod?.inputs || [] + const showContractFields = !!contract && contract.methods.length > 0 && !showHexEncodedData + const isPayableMethod = !!contract && contractMethod?.payable + + const isValueInputVisible = showHexEncodedData || !showContractFields || isPayableMethod + + useEffect(() => { + const contractFieldsValues = getValues(CONTRACT_VALUES_FIELD_NAME) + const methodValues = contractFieldsValues?.[`method-${contractMethodIndex}`] + + if (showHexEncodedData && contractMethod) { + const encodeData = encodeToHexData(contractMethod, methodValues) + setValue(CUSTOM_TRANSACTION_DATA_FIELD_TYPE, encodeData || '') + } + }, [contractMethod, getValues, setValue, showHexEncodedData, contractMethodIndex]) + + // Resets form to initial values if the user edited contract method and then switched to custom data and edited it + useEffect(() => { + if ( + showHexEncodedData && + dirtyFields[CONTRACT_METHOD_INDEX_FIELD_NAME] && + dirtyFields[CUSTOM_TRANSACTION_DATA_FIELD_NAME] + ) { + reset({ + ...initialValues, + [TO_ADDRESS_FIELD_NAME]: toAddress, + [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: customTransactionData, + [NATIVE_VALUE_FIELD_NAME]: nativeValue, + }) + } + }, [ + dirtyFields, + reset, + showHexEncodedData, + customTransactionData, + toAddress, + nativeValue, + initialValues, + ]) + + useEffect(() => { + if (isSubmitSuccessful) { + reset({ ...initialValues, [TO_ADDRESS_FIELD_NAME]: toAddress }) + } + }, [isSubmitSuccessful, reset, toAddress, initialValues]) + + return ( + <> +
+ {/* To Address field */} + + + {/* Native Token Amount Input */} + {isValueInputVisible && ( + + )} + + {/* Contract Section */} + + {/* Contract Method Selector */} + {showContractFields && ( + ({ + id: index.toString(), + label: method.name, + }))} + required + /> + )} + + {/* Contract Fields */} + {contractFields.map((contractField, index) => { + const name = `${CONTRACT_VALUES_FIELD_NAME}.method-${contractMethodIndex}.${ + contractField.name || index + }` + const fieldType = getInputTypeHelper(contractField) + + return ( + showContractFields && ( + + ) + ) + })} + + {/* Hex encoded textarea field */} + {showHexEncodedData && ( + + )} + {/* action buttons as a children */} + {children} + + + {/* set up the dev tool only in dev env */} + {!isProdEnv && } + + ) +} + +export default SolidityForm diff --git a/integrations/safe/components/tx-builder/forms/fields/AddressContractField.tsx b/integrations/safe/components/tx-builder/forms/fields/AddressContractField.tsx new file mode 100644 index 00000000..194c74be --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/fields/AddressContractField.tsx @@ -0,0 +1,35 @@ +import { ReactElement } from 'react' +import { AddressInput } from '@gnosis.pm/safe-react-components' + +const AddressContractField = ({ + id, + name, + value, + onChange, + label, + error, + getAddressFromDomain, + networkPrefix, + onBlur, +}: any): ReactElement => { + return ( + + ) +} + +export default AddressContractField diff --git a/integrations/safe/components/tx-builder/forms/fields/Field.tsx b/integrations/safe/components/tx-builder/forms/fields/Field.tsx new file mode 100644 index 00000000..f28ff40a --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/fields/Field.tsx @@ -0,0 +1,121 @@ +import { ReactElement } from 'react' +import { Control, Controller } from 'react-hook-form' +import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select' + +import { + BOOLEAN_FIELD_TYPE, + CONTRACT_METHOD_FIELD_TYPE, + CUSTOM_TRANSACTION_DATA_FIELD_TYPE, + isAddressFieldType, + isBooleanFieldType, +} from './fields' +import AddressContractField from './AddressContractField' +import SelectContractField from './SelectContractField' +import TextareaContractField from './TextareaContractField' +import TextContractField from './TextContractField' +import validateField, { ValidationFunction } from '../validations/validateField' + +const CUSTOM_DEFAULT_VALUES: CustomDefaultValueTypes = { + [BOOLEAN_FIELD_TYPE]: 'true', + [CONTRACT_METHOD_FIELD_TYPE]: '0', // first contract method as default +} + +const BOOLEAN_DEFAULT_OPTIONS: SelectItem[] = [ + { id: 'true', label: 'True' }, + { id: 'false', label: 'False' }, +] + +const DEFAULT_OPTIONS: DefaultOptionTypes = { + [BOOLEAN_FIELD_TYPE]: BOOLEAN_DEFAULT_OPTIONS, +} + +interface CustomDefaultValueTypes { + [key: string]: string +} + +interface DefaultOptionTypes { + [key: string]: SelectItem[] +} + +type FieldProps = { + fieldType: string + control: Control + id: string + name: string + label: string + fullWidth?: boolean + required?: boolean + validations?: ValidationFunction[] + getAddressFromDomain?: (name: string) => Promise + networkPrefix?: string + showErrorsInTheLabel?: boolean + shouldUnregister?: boolean + options?: SelectItem[] +} + +const Field = ({ + fieldType, + control, + name, + shouldUnregister = true, + options, + required = true, + validations, // you can define extra validations as a prop + ...props +}: FieldProps) => { + // Component based on the field type + const FieldComponent = getFieldComponent(fieldType) + + // see https://react-hook-form.com/advanced-usage#ControlledmixedwithUncontrolledComponents + return ( + ( + + )} + /> + ) +} + +export default Field + +// Returns a custom Field Component based on the field type +const getFieldComponent = (fieldType: string): ((props: any) => ReactElement) => { + if (isAddressFieldType(fieldType)) { + return AddressContractField + } + + if (isBooleanFieldType(fieldType)) { + return SelectContractField + } + + if (fieldType === CONTRACT_METHOD_FIELD_TYPE) { + return SelectContractField + } + + if (fieldType === CUSTOM_TRANSACTION_DATA_FIELD_TYPE) { + return TextareaContractField + } + + // Textfield Component as fallback + return TextContractField +} diff --git a/integrations/safe/components/tx-builder/forms/fields/JsonField.tsx b/integrations/safe/components/tx-builder/forms/fields/JsonField.tsx new file mode 100644 index 00000000..8c4c86e7 --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/fields/JsonField.tsx @@ -0,0 +1,197 @@ +import { useState, useCallback, ClipboardEvent } from 'react' +import styled from 'styled-components' +import { + Icon, + TextFieldInput, + Tooltip, + GenericModal, + Text, + Button, + IconTypes, +} from '@gnosis.pm/safe-react-components' +import IconButton from '@material-ui/core/IconButton' +import { Box } from '@material-ui/core' +import useModal from '../../../hooks/useModal/useModal' + +const DEFAULT_ROWS = 4 + +type Props = { + id: string + name: string + label: string + value: string + onChange: (value: string) => void +} + +const JsonField = ({ id, name, label, value, onChange }: Props) => { + const { open: showReplaceModal, toggleModal } = useModal() + const [tempAbi, setTempAbi] = useState(value) + const [isPrettified, setIsPrettified] = useState(false) + const hasError = isValidJSON(value) ? undefined : 'Invalid JSON value' + + const toggleFormatJSON = useCallback(() => { + if (!value) { + return + } + + try { + onChange(JSON.stringify(JSON.parse(value), null, isPrettified ? 0 : 2)) + setIsPrettified(!isPrettified) + } catch (e) { + console.error(e) + onChange(value) + } + }, [onChange, value, isPrettified]) + + const changeAbi = useCallback(() => { + onChange(tempAbi) + setIsPrettified(false) + toggleModal() + }, [tempAbi, onChange, toggleModal]) + + const handlePaste = useCallback( + (event: ClipboardEvent) => { + event.preventDefault() + event.stopPropagation() + + const clipboardData = event.clipboardData + const pastedData = clipboardData?.getData('Text') || '' + + if (value && pastedData) { + setTempAbi(pastedData) + toggleModal() + } else { + onChange(pastedData) + } + }, + [onChange, toggleModal, value], + ) + + return ( + <> + + { + onChange(event.target.value) + }} + spellCheck={false} + showErrorsInTheLabel={false} + error={hasError} + /> + + + {!isPrettified && ( + + )} + {isPrettified && ( + + )} + + + + {showReplaceModal && ( + + Do you want to replace the current ABI? + + } + onClose={toggleModal} + title="Replace ABI" + footer={ + + + + + } + /> + )} + + ) +} + +const isValidJSON = (value: string | undefined) => { + if (value) { + try { + JSON.parse(value) + } catch { + return false + } + } + + return true +} + +const IconContainerButton = ({ + tooltipLabel, + iconType, + onClick, + error, +}: { + tooltipLabel: string + iconType: IconTypes + onClick: () => void + error: boolean +}) => ( + + + + + +) + +const JSONFieldContainer = styled.div` + position: relative; +` + +const IconContainer = styled.div<{ error: boolean }>` + position: absolute; + top: -10px; + right: 15px; + border: 1px solid + ${({ theme, error }) => (error ? theme.colors.error : theme.colors.inputDefault)}; + border-radius: 50%; + background-color: #fff; +` + +const StyledTextField = styled(TextFieldInput)` + && { + textarea { + font-family: monospace; + font-size: 12px; + &.MuiInputBase-input { + padding: 0; + } + } + } +` + +const StyledButton = styled(IconButton)` + margin: 0 5px; +` + +export default JsonField diff --git a/integrations/safe/components/tx-builder/forms/fields/SelectContractField.tsx b/integrations/safe/components/tx-builder/forms/fields/SelectContractField.tsx new file mode 100644 index 00000000..75b0c902 --- /dev/null +++ b/integrations/safe/components/tx-builder/forms/fields/SelectContractField.tsx @@ -0,0 +1,40 @@ +import { Select } from '@gnosis.pm/safe-react-components' +import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select' + +type SelectContractFieldTypes = { + options: SelectItem[] + onChange: (id: string) => void + value: string + label: string + name: string + id: string +} + +const SelectContractField = ({ + value, + onChange, + options, + label, + name, + id, +}: SelectContractFieldTypes) => { + return ( +