-
Notifications
You must be signed in to change notification settings - Fork 2
/
batch-transfer.html
167 lines (148 loc) · 8.25 KB
/
batch-transfer.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<title>NFT Life</title>
<style>
#flash {
transform: rotate(-10deg);
}
#flash h1 {
font-family: 'Courier New';
}
body {
background: linear-gradient(222deg, #065541, #5b148d);
background-size: 400% 400%;
animation: AnimationName 7s ease infinite;
}
@keyframes AnimationName {
0%{background-position:0% 50%}
50%{background-position:100% 50%}
100%{background-position:0% 50%}
}
</style>
</head>
<body class="text-light vstack h-100 lead">
<header id="flash" class="pricing-header my-5 py-lg-5 mx-auto">
<h1 class="display-1">NFT LIFE</h1>
</header>
<main role="main" class="row w-75 mx-auto">
<p>👋 Free to use for <a target="_blank" href="https://tenthousandsu.com">Su Squares</a><!-- and everybody else, but no need to waste space on the page saying that -->!</p>
<h1>Batch transfer NFTs</h1>
<p class="mb-5">This tool allows you send multiple tokens you own to another address in one transaction. This can be significantly cheaper than sending tokens one-at-a-time.</p>
<div class="mb-3">
<label for="network" class="form-label">Network</label>
<select class="form-select" id="network">
<option selected>PICK A NETWORK</option>
<option value="ethereummainnet">Ethereum Mainnet</option>
<option value="ethereumropsten">Ethereum Ropsten test network</option>
</select>
</div>
<div class="mb-3">
<label for="contract" class="form-label">NFT contract address</label>
<input class="form-control" id="contract" placeholder="0x...">
</div>
<div class="mb-3">
<label for="recipient" class="form-label">Recipient address</label>
<input class="form-control" id="recipient" placeholder="0x...">
</div>
<div class="mb-3">
<label for="tokenids" class="form-label">Token IDs (one per line, in decimal numbers)</label>
<textarea class="form-control" id="tokenids" rows="3"></textarea>
</div>
<div class="hstack gap-3 mb-3">
<button id="btnapprove" class="btn btn-lg btn-secondary">Approve contract</button>
Only need to do this once per NFT contract address
</div>
<div class="hstack gap-3 mb-3">
<button id="btntransfer" class="btn btn-lg btn-primary">Transfer</button>
No platform fee, gas cost is linear to number of tokens
</div>
<hr class="my-5">
<p>The batch transfer contracts are:</p>
<ul>
<li>Ethereum Mainnet: <a target="_blank" href="https://etherscan.io/address/0x2e2234b3a848f895a60b2071f90303cd02f7491d#code">0x2E2234b3A848F895a60B2071F90303CD02f7491D</a></li>
<li>Ethereum Ropsten: <a target="_blank" href="https://ropsten.etherscan.io/address/0x0dFCB9937B446E217d6d07acca9cd5d9B421e28B#code">0x0dFCB9937B446E217d6d07acca9cd5d9B421e28B</a></li>
</ul>
</main>
<footer class="text-center my-5">
<span>These tools brought to you by <a class="link-light" href="https://tenthousandsu.com">Su Squares</a> and <a class="link-light" href="https://phor.net">William Entriken</a>.</span>
</footer>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ethers.umd.min.js" integrity="sha256-hhyZwmdi3z8gOlk+hH86uTyHPFbQQ4j9JGWW/pE+kks=" crossorigin="anonymous"></script>
<script>
const transferContractAddressPerNetwork = {
ethereummainnet: "0x2E2234b3A848F895a60B2071F90303CD02f7491D", // https://etherscan.io/address/0x2e2234b3a848f895a60b2071f90303cd02f7491d#code
ethereumropsten: "0x0dFCB9937B446E217d6d07acca9cd5d9B421e28B" // https://ropsten.etherscan.io/address/0x0dFCB9937B446E217d6d07acca9cd5d9B421e28B#code
}
const transferContractAbi = [
"function batchTransfer(address tokenContract, address recipient, uint256[] calldata tokenIds)"
];
const erc721abi = [
"function setApprovalForAll(address operator, bool approved)",
"function isApprovedForAll(address owner, address operator) view returns (bool)"
];
document.getElementById("btnapprove").addEventListener("click", async function() {
await ethereum.request({ method: "eth_requestAccounts" });
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const network = document.getElementById("network").value;
if (typeof transferContractAddressPerNetwork[network] === "undefined") {
return alert("Please pick a network.");
}
const transferContractAddress = transferContractAddressPerNetwork[network];
const erc721ContractAddress = document.getElementById("contract").value;
if (erc721ContractAddress.length !== 42) {
return alert("Please fill in contract address for ERC-721 token contract.")
}
const erc721Contract = new ethers.Contract(erc721ContractAddress, erc721abi, provider);
const isAlreadyApproved = await erc721Contract.isApprovedForAll(await signer.getAddress(), transferContractAddress);
if (isAlreadyApproved) {
return alert("Contract was already approved before you clicked that button, aborting.");
}
const erc721ContractWithSigner = erc721Contract.connect(signer);
await erc721ContractWithSigner.setApprovalForAll(transferContractAddress, true);
});
document.getElementById("btntransfer").addEventListener("click", async function() {
await ethereum.request({ method: "eth_requestAccounts" });
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const network = document.getElementById("network").value;
if (typeof transferContractAddressPerNetwork[network] === "undefined") {
return alert("Please pick a network.");
}
const transferContractAddress = transferContractAddressPerNetwork[network];
const erc721ContractAddress = document.getElementById("contract").value;
if (erc721ContractAddress.length !== 42) {
return alert("Please fill in contract address for ERC-721 token contract.")
}
const recipientAddress = document.getElementById("recipient").value;
if (recipientAddress.length !== 42) {
return alert("Please fill in token recipient address.")
}
if (erc721ContractAddress === recipientAddress) {
alert("You are about to send tokens to the contract address. If this extra warning saved you, please make a tweet mentioning @fulldecent and kwyjibo.")
}
const erc721Contract = new ethers.Contract(erc721ContractAddress, erc721abi, provider);
const isAlreadyApproved = await erc721Contract.isApprovedForAll(await signer.getAddress(), transferContractAddress);
if (!isAlreadyApproved) {
return alert("You did not approve contract for batch transfer yet. Please do that first. Aborted batch transfer.");
}
const tokenIds = document.getElementById("tokenids").value.trim().split(/\s+/);
if (tokenIds.length === 0 || (tokenIds.length === 1 && tokenIds[0] === '')) {
return alert("Please enter token IDs to transfer.");
}
for (let i = 0; i < tokenIds.length; i++) {
if (!tokenIds[i].match(/^\d+$/)) {
return alert("Token IDs must be decimal numbers. Your token in position " + i + " was " + tokenIds[i]);
}
}
const tokenIdsBigNumbers = tokenIds.map(x => ethers.BigNumber.from(x));
const transferContract = new ethers.Contract(transferContractAddress, transferContractAbi, provider);
const transferContractWithSigner = transferContract.connect(signer);
await transferContractWithSigner.batchTransfer(erc721ContractAddress, recipientAddress, tokenIds);
});
</script>
</body>
</html>