Skip to content

Commit

Permalink
Nft transfers (#311)
Browse files Browse the repository at this point in the history
* First working version of nft transfers

* UI changes for multisending nfts and erc20 tokens in one transaction

NEW UI:
* Tab-Navigation to fill out a CSV for Asset-Transfers or Collectible-Transfers
* transfer-tables are now always displayed under the CSV-Form.
* there are now two tables: For asset transfers and for collectible transfers
* submit button is at the bottom of these tables

issue #16

* small ui cleanup

* Cache for erc721 token info provider

* rework of nft sending ui

* one combined CSV file for nft / asset transfers
* tables are wrapped in accordions

* erc1155 support, some redesigns

* small refactoring of modal

* fixes existing unittests

* small refactoring, parser tests

* finishes up nft transfers

* Updates help text
* Validates, that the value is a integer for erc1155 transfers
* unittests for the transfer of collectibles
* unittest for decimal (invalid) erc1155
* fixes sample file

* remove unused global styles

* simplifies token_types erc1155 and erc721 to nft

* instead of providing erc1155/erc721 the token_type now is simply nft
* fixes performance problem of editor. For no reason the csvContent was held by the App and passed down to the editor
* tests for nft transfers
* updated faq

* Update src/components/FAQModal.tsx

Co-authored-by: schmanu <[email protected]>
Co-authored-by: Benjamin Smith <[email protected]>
  • Loading branch information
3 people authored Dec 3, 2021
1 parent 712f791 commit 86de397
Show file tree
Hide file tree
Showing 32 changed files with 1,945 additions and 532 deletions.
327 changes: 327 additions & 0 deletions customabis/ERC721.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"fmt": "prettier --check '**/*.ts'",
"fmt:write": "prettier --write '**/*.ts'",
"prepare": "husky install",
"generate-types": "typechain --target=ethers-v5 --out-dir src/contracts './node_modules/@openzeppelin/contracts/build/contracts/ERC20.json'",
"generate-types": "typechain --target=ethers-v5 --out-dir src/contracts './node_modules/@openzeppelin/contracts/build/contracts/ERC20.json' './customabis/ERC721.json' './node_modules/@openzeppelin/contracts/build/contracts/ERC1155.json' './node_modules/@openzeppelin/contracts/build/contracts/ERC165.json'",
"postinstall": "yarn generate-types"
},
"dependencies": {
Expand Down
9 changes: 5 additions & 4 deletions public/sample.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
token_address,receiver,amount
0x6810e776880c02933d47db1b9fc05908e5386b96,0x1000000000000000000000000000000000000000,0.0001
0x6b175474e89094c44da98b954eedeac495271d0f,0x2000000000000000000000000000000000000000,0.0001
,0x3000000000000000000000000000000000000000,0.0001
token_type,token_address,receiver,value,id
erc20,0x6810e776880c02933d47db1b9fc05908e5386b96,0x1000000000000000000000000000000000000000,0.0001
erc20,0x6b175474e89094c44da98b954eedeac495271d0f,0x2000000000000000000000000000000000000000,0.0001
native,,0x3000000000000000000000000000000000000000,0.0001
erc721,0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85,0x4000000000000000000000000000000000000000,,42
124 changes: 63 additions & 61 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk";
import { GenericModal, Icon, Loader, Text, Title, Divider, Button } from "@gnosis.pm/safe-react-components";
import { Fab } from "@material-ui/core";
import { BaseTransaction } from "@gnosis.pm/safe-apps-sdk";
import { Button, Card, Divider, Loader, Text } from "@gnosis.pm/safe-react-components";
import { setUseWhatChange } from "@simbathesailor/use-what-changed";
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import styled from "styled-components";

import { CSVForm } from "./components/CSVForm";
import { FAQModal } from "./components/FAQModal";
import { Header } from "./components/Header";
import { Summary } from "./components/Summary";
import { CSVForm } from "./components/assets/CSVForm";
import { useTokenList, networkMap } from "./hooks/token";
import { AssetTransfer, CollectibleTransfer, Transfer } from "./parser/csvParser";
import { buildAssetTransfers, buildCollectibleTransfers } from "./transfers/transfers";

setUseWhatChange(process.env.NODE_ENV === "development");

const App: React.FC = () => {
const { isLoading } = useTokenList();
const { safe } = useSafeAppsSDK();
const [showHelp, setShowHelp] = useState(false);
const [tokenTransfers, setTokenTransfers] = useState<Transfer[]>([]);

const [submitting, setSubmitting] = useState(false);
const [parsing, setParsing] = useState(false);
const { sdk } = useSafeAppsSDK();

const assetTransfers = tokenTransfers.filter(
(transfer) => transfer.token_type === "erc20" || transfer.token_type === "native",
) as AssetTransfer[];
const collectibleTransfers = tokenTransfers.filter(
(transfer) => transfer.token_type === "erc1155" || transfer.token_type === "erc721",
) as CollectibleTransfer[];

const submitTx = useCallback(async () => {
setSubmitting(true);
try {
const txs: BaseTransaction[] = [];
txs.push(...buildAssetTransfers(assetTransfers));
txs.push(...buildCollectibleTransfers(collectibleTransfers));

console.log(`Encoded ${txs.length} transfers.`);
const sendTxResponse = await sdk.txs.send({ txs });
const safeTx = await sdk.txs.getBySafeTxHash(sendTxResponse.safeTxHash);
console.log({ safeTx });
} catch (e) {
console.error(e);
}
setSubmitting(false);
}, [assetTransfers, collectibleTransfers, sdk.txs]);

return (
<Container>
<Header />
Expand All @@ -26,67 +59,36 @@ const App: React.FC = () => {
<Text size={"lg"}>Loading Tokenlist...</Text>
</>
) : (
<CSVForm />
<Card className="cardWithCustomShadow">
<CSVForm updateTransferTable={setTokenTransfers} setParsing={setParsing} />
<Divider />
<Summary assetTransfers={assetTransfers} collectibleTransfers={collectibleTransfers} />
{submitting ? (
<>
<Loader size="md" />
<br />
<Button size="lg" color="secondary" onClick={() => setSubmitting(false)}>
Cancel
</Button>
</>
) : (
<Button
style={{ alignSelf: "flex-start", marginTop: 16, marginBottom: 16 }}
size="lg"
color="primary"
onClick={submitTx}
disabled={parsing || tokenTransfers.length + collectibleTransfers.length === 0}
>
{parsing ? <Loader size="sm" color="primaryLight" /> : "Submit"}
</Button>
)}
</Card>
)}
</>
) : (
<Text size={"xl"}>Network with chainId {safe.chainId} not yet supported.</Text>
)}
<Fab
variant="extended"
size="small"
style={{ position: "absolute", top: 24, right: 24, textTransform: "none" }}
onClick={() => setShowHelp(true)}
>
<Icon size="md" type="question" />
<Text size="xl">Help</Text>
</Fab>
{showHelp && (
<GenericModal
onClose={() => setShowHelp(false)}
title={<Title size="lg">How to use the CSV Airdrop Gnosis App</Title>}
body={
<div>
<Title size="md" strong>
Preparing a Transfer File
</Title>
<Text size="lg">
Transfer files are expected to be in CSV format with the following required columns:
<ul>
<li>
<code>receiver</code>: Ethereum address of transfer receiver.
</li>
<li>
<code>token_address</code>: Ethereum address of ERC20 token to be transferred.
</li>
<li>
<code>amount</code>: the amount of token to be transferred.
</li>
</ul>
<p>
<b>
Important: The CSV file has to use "," as a separator and the header row always has to be provided
as the first row and include the described column names.
</b>
</p>
</Text>
<Divider />
<Title size="md" strong>
Native Token Transfers
</Title>
<Text size="lg">
Since native tokens do not have a token address, you must leave the <code>token_address</code> column
blank for native transfers.
</Text>
</div>
}
footer={
<Button size="md" color="secondary" onClick={() => setShowHelp(false)}>
Close
</Button>
}
></GenericModal>
)}
<FAQModal />
</Container>
);
};
Expand Down
11 changes: 11 additions & 0 deletions src/GlobalStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ const GlobalStyle = createGlobalStyle`
.MuiPaper-elevation3 {
box-shadow: 0px 3px 3px -2px #F7F5F5,0px 3px 4px 0px #F7F5F5,0px 1px 8px 0px #F7F5F5 !important;
}
.navLabel {
flex: 1;
}
.tableContainer {
display: flex;
flex-direction: horizontal;
gap: 16px;
width: 100%;
}
`;

export default GlobalStyle;
Loading

0 comments on commit 86de397

Please sign in to comment.