Skip to content
This repository was archived by the owner on Oct 11, 2023. It is now read-only.

Commit dad74f8

Browse files
authored
Merge pull request #69 from BossaDev/saveTx
2nd hackathon submission
2 parents 98a1574 + eaa3110 commit dad74f8

File tree

4 files changed

+152
-88
lines changed

4 files changed

+152
-88
lines changed

docs.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@
2424

2525
<h2>Motivation</h2>
2626
<p>Some say that flashloans democratize access to large amounts of capital, but that is not entirely true, until they are useable by the average investor. Nowadays to access the feature one needs deep technical logic, and good coding skills. Atomic will put this and other great tools in the hands of a lot more users.</p>
27-
<p>This was a hack in the hackathon.money hackathon by ETHGlobal.</p>
2827

2928
<h2>Tech Stack</h2>
3029
<p>We started with just one smart contract, that would send one transaction after another on behalf of the user. The atomic functionality is still contained in a single contract, that creates proxies for user using CREATE2 (addresses are unique per user). We also had to develop a proxy contract for all "sandwich" blocks, or blocks that need to handle a callback like flashloans.</p>
3130
<p>We used Buidler to perform tests, also using a fork from the mainnet in Ganache.</p>
3231
<p>For the UI we forked scratch-blocks, and tweaked for our needs (removing all blocks provided by the library and adding our own, plus some other small mods). We made those changes with extensibility in mind. Each block lives in a single JS file, the way we envision this in the future is that companies will pull request blocks that use their platforms.</p>
33-
<p>We also of course used Ethers JS extensively for encoding a bunch of data and any interaction with Web3, and our choice of wallet was Portis.</p>
32+
<p>We also of course used Ethers JS extensively for encoding a bunch of data and any interaction with Web3.</p>
3433
<p>Currently there are bocks for ETH transfers, ERC20 Transfers, Uniswap swap (V1 and V2) and flashswap, Aave flashloan, Compound supplying and borrowing, Pooltogether, Defi Zaps, Balancer and an ENS resolver.</p>
3534
<p>The dapp is served from IPFS (pinned on Pinata), and is available at <a href="http://atomicninja.eth.link" target="_blank">atomicninja.eth</a> and <a href="https://atomic.ninja" target="_blank">atomic.ninja</a>.</p>
3635

@@ -57,6 +56,19 @@ <h3><img src='./media/zoom-simulateAtomic.svg'> Simulate</h3>
5756
<p>Press this button to simulate the call. Your call to the atomic contract will be encoded exactly as when you send it, but will be sent unsigned to a public node. This will indicate if the transaction will fail or succeed using the state of the latest mainnet block. Currently no feedback on failure is provided (the node provides no details on failures), if your transaction is failing, take a look and check if all your blocks have the funds to perform their action at the point of execution.</p>
5857
<h3><img src='./media/zoom-launchAtomic.svg'> Launch Atomic Tx</h3>
5958
<p>Lift off! Use this button to send out your atomic (mainnet only). This is currently disabled as we feel the tool is not ready to handle funds. Remember, this is hackathon code.</p>
59+
<h3>Transaction Menu</h3>
60+
<h4>Save</h4>
61+
<p>Will save the transaction to your local storage (no servers unfortunately :) so if you want to open the transaction in a different device you'll need to share it).</p>
62+
<h4>Open</h4>
63+
<p>You'll be presented with a list of previously saved ninja transactions.</p>
64+
<h4>Share</h4>
65+
<p>Sharing allows you to send a url out to friends or in social media. When you press share, your transaction will be uploaded to IPFS. The link to open it is copied to your clipboard, so you can paste it anywhere.</p>
66+
<p>Remember that IPFS is efemeral by nature, if your transaction is not used for long enough the files will no longer be available. If you need your transactions to persist (if embedding in your website for example), we recommend using <a href="https://pinata.cloud" target="_blank">IPFS Pinata</a>, they provide 1gb in their free tier, enough for millions of ninja transactions. Our front end is pinned in their free tier :)</p>
67+
<h4>Embed</h4>
68+
<p>This allows you to embed any transaction in your website. Embeded transactions are not editable by nature, but if the user clicks on the Atomic Ninja icon he will be taken to a full fledged editor. This features works particularly well on mobile.</p>
69+
<p>Funcitonal example (thank you for sending this one out 😊):</p>
70+
<iframe width="560" height="315" src="./index.html?&tx=QmQxEnYR2ztMah5eTjRekbQkgZ74iv97BqSrifPjSf1kZb&embed=true" frameborder="0" allowfullscreen></iframe>
71+
6072

6173
<h2>How it works</h2>
6274
<p>When you start a ninja transaction a call is made for the atomic smart contract. It then creates a proxy for the user (using Create2, proxy addresses are unique per user) that then sequentially executes the transactions. Let's look with more detail into an actual transaction:</p>
@@ -83,7 +95,7 @@ <h2>How it works</h2>
8395
<h2>Launch vs. Simulate</h2>
8496
<p>Both will pack the transactions the exact same way.</p>
8597
<p>When you press simulate, atomic sends an eth_call (the one used to read from smart contracts) to a public node, the return value will tell you if it succeeds. If it fails, we made some EVM hackery to return the revert message from the <i>original</i> revert. This means that you will see a revert message from DAI smart contract for failed transfers. Some contracts of course will not have a message, and then you will see a generic error. Failed ETH transfers also do not give any feedback. We have already nailed how to present to the user the block in which the ninja tx failed, but have yet to implement it.</p>
86-
<p>When you press launch, you will be prompted to sign the transaction, and it will be bradcasted by Portis.</p>
98+
<p>When you press launch, you will be prompted to sign the transaction. Lift off 🚀!</p>
8799

88100
<p>We sent the above example out, you can check it on <a href="https://etherscan.io/tx/0x72b7156c60d8a94fa074497c360e20cb96b02d85b45e13a137049fa3a469d38f" target="_blank">Etherscan</a> or with much more detail on <a href="https://dashboard.tenderly.co/tx/main/0x72b7156c60d8a94fa074497c360e20cb96b02d85b45e13a137049fa3a469d38f" target="_blank">Tenderly</a>.</p>
89101
<p>Below are the Etherscan verified contracts the transaction interacted with, also courtesy of Tenderly:</p>

index.html

Lines changed: 25 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77

88
<title>Atomic Ninja</title>
99

10+
<script>
11+
var loc = window.location.href + '';
12+
if (loc.indexOf('http://') == 0 &&
13+
!(loc.indexOf('http://localhost:8000/') == 0 ||
14+
loc.indexOf('http://0.0.0.0:8000/') == 0 ||
15+
loc.indexOf('file:///') == 0)
16+
) {
17+
console.log("Redirecting to SSL connection.")
18+
window.location.href = loc.replace('http://', 'https://');
19+
}
20+
</script>
21+
1022
<!-- This exposes the library as a global variable: ethers -->
1123
<script src="./js/lib/ethers.min.js" charset="utf-8" type="text/javascript"></script>
1224
<script src="./js/lib/blockly_compressed_vertical.js"></script>
@@ -44,74 +56,11 @@
4456
<script type="text/javascript" src="./js/lib/buffer.js"></script>
4557
<script src="./js/setup.js"></script>
4658

47-
<script>
48-
'use strict';
49-
50-
var loc = window.location.href + '';
51-
if (loc.indexOf('http://') == 0 &&
52-
!(loc.indexOf('http://localhost:8000/') == 0 ||
53-
loc.indexOf('http://0.0.0.0:8000/') == 0 ||
54-
loc.indexOf('file:///') == 0)
55-
) {
56-
console.log("Redirecting to SSL connection.")
57-
window.location.href = loc.replace('http://', 'https://');
58-
}
59-
60-
var workspace = null;
61-
62-
function start() {
63-
64-
// Create main workspace.
65-
workspace = Blockly.inject('blocklyDiv', {
66-
comments: true,
67-
disable: false,
68-
collapse: false,
69-
media: './media/',
70-
readOnly: false,
71-
rtl: null,
72-
scrollbars: true,
73-
toolbox: getToolboxElement(),
74-
toolboxPosition: "start",
75-
horizontalLayout: false,
76-
sounds: true,
77-
zoom: {
78-
controls: true,
79-
wheel: true,
80-
startScale: 0.85,
81-
maxScale: 4,
82-
minScale: 0.25,
83-
scaleSpeed: 1.1
84-
},
85-
colours: {
86-
fieldShadow: 'rgba(255, 255, 255, 0.3)',
87-
dragShadowOpacity: 0.6
88-
}
89-
});
90-
91-
picoModal(
92-
"<h2>Welcome to Atomic Ninja!</h2>" +
93-
"<p>Send complex atomic transactions to Ethereum like never before.</p>" +
94-
"<h3>Quick Start</h3>" +
95-
"<p>On the left of the screen there is a list of sorted, disconnected blocks. This is your <b>toolbox</b>.</p>" +
96-
"<p>Drag the blocks to the <b>workspace</b> at the right, join them and edit their parameters to perform the actions you´d like.</p>" +
97-
"<p>Start with a hat shaped block such as <img src='./media/hatblock.png' style='width: 11%;'>, as they indicate the beginning of your transaction.</p>" +
98-
"<p>Once you are done, press <img src='./media/zoom-simulateAtomic.svg'> to simulate your transaction, and <img src='./media/zoom-launchAtomic.svg'> to send it out!</p>" +
99-
"<p><b>Tip:</b> Hover the blocks for details on how they work.</p>" +
100-
"<p>For more details, head over to the <a href='./docs.html'>Atomic documentation</a>.</p>"
101-
).show();
102-
}
103-
104-
function getToolboxElement() {
105-
var match = location.search.match(/toolbox=([^&]+)/);
106-
return document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
107-
}
108-
</script>
109-
11059
<style>
11160
html,
11261
body {
11362
height: 100%;
114-
background-color: lightgrey;
63+
background-color: transparent;
11564
font-family: sans-serif;
11665
overflow: hidden;
11766
}
@@ -174,25 +123,28 @@
174123
</style>
175124
</head>
176125

177-
<body onload="start()">
178-
<nav>
126+
<body onload="setup()">
127+
<div id="embedLogo">
128+
<a id="embedLink" href="#" target="_blank"><img src='./media/atomic-ninja-icon.png'
129+
style="width: 30px; position: relative; top: 20px; left: 10px; z-index: 101 !important;"></a>
130+
</div>
131+
<nav id="navbar">
179132
<ul class="menu">
180-
<li><img src='./media/atomic-ninja-icon.png' style="width: 30px; margin-right: 25px; position: relative; top: -4px;">
133+
<li><img src='./media/atomic-ninja-icon.png'
134+
style="width: 30px; margin-right: 25px; position: relative; top: -4px;">
181135
</li>
182136
<li><a href="#">Transaction</a>
183137
<ul>
184138
<li><a href="#" onclick="saveAtomic()">Save</a></li>
185139
<li><a href="#" onclick="openMenu()">Open</a></li>
186140
<li><a href="#" onclick="shareAtomic()">Share</a></li>
187-
<li><a href="#">Embed</a></li>
141+
<li><a href="#" onclick="embedAtomic()">Embed</a></li>
188142
</ul>
189143
</li>
190-
<li><a
191-
href="./docs.html">Docs</a>
144+
<li><a href="./docs.html">Docs</a>
145+
</li>
146+
<li><a href="mailto:[email protected]">Contact</a>
192147
</li>
193-
<li><a
194-
href="mailto:[email protected]">Contact</a>
195-
</li>
196148
</ul>
197149
</nav>
198150
<div id="blocklyDiv"></div>

js/saveAtomic.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const copyToClipboard = str => {
88
el.select();
99
document.execCommand('copy');
1010
document.body.removeChild(el);
11-
};
11+
};
1212

1313
const saveAtomic = () => {
1414
let xml = Blockly.Xml.workspaceToDom(Blockly.mainWorkspace)
@@ -20,13 +20,25 @@ const saveAtomic = () => {
2020
const openAtomic = (txName) => {
2121
// let txName = prompt("Open transaction", "My Atomic Tx");
2222
let xml = Blockly.Xml.textToDom(localStorage.getItem(txName))
23-
console.log(xml)
2423

2524
Blockly.mainWorkspace.clear()
2625
Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xml)
2726
}
2827

28+
var modal
29+
2930
const openMenu = () => {
31+
let list = JSON.parse(localStorage.getItem('txList'));
32+
33+
let modalContent = "<h2>Open Transaction</h2>"
34+
for (i = 0; i < list.length; i++)
35+
modalContent += "<h4><a href='#' onclick='openAtomic(\"" + list[i] + "\");modal.close()'> " + list[i] + "</a><h4>"
36+
37+
modal = picoModal(modalContent).show();
38+
return
39+
40+
console.log(list)
41+
3042
let txName = prompt("Please type the name of the tx to open (we'll improve this soon).", "My Atomic Tx");
3143
openAtomic(txName)
3244
}
@@ -55,6 +67,30 @@ const shareAtomic = () => {
5567
});
5668
}
5769

70+
const embedAtomic = () => {
71+
const ipfs = IpfsHttpClient('ipfs.infura.io', '5001', {
72+
protocol: "https"
73+
})
74+
75+
let xml = Blockly.Xml.workspaceToDom(Blockly.mainWorkspace)
76+
77+
const transaction = buffer.Buffer(Blockly.Xml.domToText(xml))
78+
ipfs.add(transaction, (err, result) => {
79+
if (err) {
80+
console.error(err)
81+
return
82+
} else {
83+
console.log(result)
84+
console.log('https://ipfs.io/ipfs/' + result[0].hash)
85+
copyToClipboard("https://atomic.ninja/?&tx=" + result[0].hash + "&embed=true")
86+
alert("URL copied to clipboard. iFrame it on your webpage and you'll be good to go (SSL required). \n\n It's on IPFS, so if you want it to persist overtime you have to pin it. We recomment https://pinata.cloud/, they allow for pinning of thousasnds of transactions in their free tier. That said, your transaction should be good for the coming days. \n\n The IPFS hash of the transaction is: " + result[0].hash)
87+
88+
// copying to clipboard
89+
90+
}
91+
});
92+
}
93+
5894
function addToTxList(txName) {
5995
var list;
6096
//is anything in localstorage?
@@ -63,6 +99,7 @@ function addToTxList(txName) {
6399
} else {
64100
list = JSON.parse(localStorage.getItem('txList'));
65101
}
66-
list.push(txName);
102+
if (!list.includes(txName))
103+
list.push(txName);
67104
localStorage.setItem('txList', JSON.stringify(list));
68105
}

js/setup.js

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
'use strict';
2+
3+
var workspace = null;
4+
15
// routines that run when starting
26
function getUrlVars() {
37
var vars = {};
@@ -7,30 +11,89 @@ function getUrlVars() {
711
return vars;
812
}
913

10-
14+
function getToolboxElement() {
15+
var match = location.search.match(/toolbox=([^&]+)/);
16+
return document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
17+
}
1118

1219
async function setup() {
20+
21+
1322
let urlVars = getUrlVars()
1423

1524
const ipfs = IpfsHttpClient('ipfs.infura.io', '5001', {
1625
protocol: "https"
1726
})
1827

28+
let readOnly = false
29+
let scrollbars = true
30+
if (urlVars.embed) {
31+
// hide stuff
32+
document.getElementById("navbar").style.display = "none";
33+
document.getElementById("blocklyDiv").style.height = "100%";
34+
document.getElementById("blocklyDiv").style.top = "-30px";
35+
document.getElementById("embedLink").href = "https://atomic.ninja?&tx=" + urlVars.tx;
36+
readOnly = true
37+
scrollbars = false
38+
} else {
39+
document.getElementById("embedLogo").style.display = "none";
40+
}
41+
42+
// Create main workspace.
43+
workspace = Blockly.inject('blocklyDiv', {
44+
comments: true,
45+
disable: false,
46+
collapse: false,
47+
media: './media/',
48+
readOnly: readOnly,
49+
rtl: null,
50+
scrollbars: scrollbars,
51+
toolbox: getToolboxElement(),
52+
toolboxPosition: "start",
53+
horizontalLayout: false,
54+
sounds: true,
55+
zoom: {
56+
controls: true,
57+
wheel: true,
58+
startScale: 0.85,
59+
maxScale: 4,
60+
minScale: 0.25,
61+
scaleSpeed: 1.1
62+
},
63+
colours: {
64+
fieldShadow: 'rgba(255, 255, 255, 0.3)',
65+
dragShadowOpacity: 0.6
66+
}
67+
});
68+
69+
// open tx
1970
if (urlVars.tx) {
20-
txData = await ipfs.get(urlVars.tx)
71+
72+
let txData
73+
if (urlVars.tx.substr(-1) == "#")
74+
txData = await ipfs.get(urlVars.tx.slice(0, -1))
75+
else
76+
txData = await ipfs.get(urlVars.tx)
77+
2178

2279
let atomicTx = new TextDecoder("utf-8").decode(txData[0].content);
2380

2481
let xml = Blockly.Xml.textToDom(atomicTx)
2582
Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xml)
2683
}
2784

28-
if (urlVars.embed) {
29-
// hide stuff
30-
console.log("hide stuff")
31-
}
85+
if (!urlVars.embed)
86+
picoModal(
87+
"<h2>Welcome to Atomic Ninja!</h2>" +
88+
"<p>Send complex atomic transactions to Ethereum like never before.</p>" +
89+
"<h3>Quick Start</h3>" +
90+
"<p>On the left of the screen there is a list of sorted, disconnected blocks. This is your <b>toolbox</b>.</p>" +
91+
"<p>Drag the blocks to the <b>workspace</b> at the right, join them and edit their parameters to perform the actions you´d like.</p>" +
92+
"<p>Start with a hat shaped block such as <img src='./media/hatblock.png' style='width: 11%;'>, as they indicate the beginning of your transaction.</p>" +
93+
"<p>Once you are done, press <img src='./media/zoom-simulateAtomic.svg'> to simulate your transaction, and <img src='./media/zoom-launchAtomic.svg'> to send it out!</p>" +
94+
"<p><b>Tip:</b> Hover the blocks for details on how they work.</p>" +
95+
"<p>For more details, head over to the <a href='./docs.html'>Atomic documentation</a>.</p>"
96+
).show();
3297

3398

34-
}
35-
setup()
36-
99+
}

0 commit comments

Comments
 (0)