Skip to content

Commit 78628d8

Browse files
committed
account abstraction
add account account abstraction layer
0 parents  commit 78628d8

23 files changed

+16993
-0
lines changed

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
2+
# SCROLL_TESTNET_URL=https://prealpha-rpc.scroll.io/l2 - Uncomment this line to use the pre-alpha instead of alpha
3+
SCROLL_TESTNET_URL=https://alpha-rpc.scroll.io/l2

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
node_modules
2+
.env
3+
coverage
4+
coverage.json
5+
typechain
6+
typechain-types
7+
8+
#Hardhat files
9+
cache
10+
artifacts
11+
12+
# IDE files
13+
.idea
14+

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Adonis Scroll Contracts
2+
3+
## Prerequisites
4+
5+
- Network setup: https://guide.scroll.io/user-guide/setup
6+
7+
8+
## Deploy with Hardhat
9+
10+
1. If you haven't already, install [nodejs](https://nodejs.org/en/download/) and [yarn](https://classic.yarnpkg.com/lang/en/docs/install).
11+
2. Run `yarn install` to install dependencies.
12+
3. Create a `.env` file following the example `.env.example` in the root directory. Change `PRIVATE_KEY` to your own account private key in the `.env`.
13+
4. Run `yarn compile` to compile the contract.
14+
5. Run `yarn deploy:scrollTestnet` to deploy the contract on the Scroll Alpha Testnet.
15+
6. Run `yarn test` for hardhat tests.
16+
17+
18+
## Deploy with Foundry
19+
20+
1. Install Foundry.
21+
```shell
22+
curl -L https://foundry.paradigm.xyz | bash
23+
foundryup
24+
```
25+
2. Build the project.
26+
```
27+
forge build
28+
```
29+
3. Deploy the contract.
30+
```
31+
forge create --rpc-url https://alpha-rpc.scroll.io/l2 \ --value 0.0000001 \ --constructor-args 1696118400 \ --private-key 8953122ef7969e14f90b8d56846fc727e18009d29ee4109a1d9027c2cb828d0d \ --legacy \ contracts/Lock.sol:Lock
32+
```
33+
- `<lock_amount>` is the amount of `ETH` to be locked in the contract. Try setting this to some small amount, like ≈.
34+
- `<unlock_time>` is the Unix timestamp after which the funds locked in the contract will become available for withdrawal. Try setting this to some Unix timestamp in the future, like `1696118400` (this Unix timestamp corresponds to October 1, 2023).
35+
36+
For example:
37+
```
38+
forge create --rpc-url https://alpha-rpc.scroll.io/l2 --value 0.00000000002ether --constructor-args 1696118400 --private-key 8953122ef7969e14f90b8d56846fc727e18009d29ee4109a1d9027c2cb828d0d --legacy contracts/Lock.sol:Lock
39+
```
40+
41+

backend.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import createAlchemyWeb3 from "@alch/alchemy-web3"
2+
import dotenv from "dotenv"
3+
import fs from "fs"
4+
import cors from "cors"
5+
import express from "express"
6+
7+
const app = express()
8+
dotenv.config();
9+
10+
const GREETER_CONTRACT_ADDRESS = "0x8b4751Bf2Ba59222EB3d40D9fe7FDD98496503b9"
11+
const BACKEND_WALLET_ADDRESS = "0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB"
12+
const GREETER_CONTRACT_ABI_PATH = "./json_abi/Greeter.json"
13+
const PORT = 8080
14+
var web3 = null
15+
var greeterContract = null
16+
17+
const loadContract = async (data) => {
18+
data = JSON.parse(data);
19+
20+
const netId = await web3.eth.net.getId();
21+
greeterContract = new web3.eth.Contract(
22+
data,
23+
GREETER_CONTRACT_ADDRESS
24+
);
25+
}
26+
27+
async function initAPI() {
28+
const { GOERLI_RPC_URL, PRIVATE_KEY } = process.env;
29+
web3 = createAlchemyWeb3.createAlchemyWeb3(GOERLI_RPC_URL);
30+
31+
fs.readFile(GREETER_CONTRACT_ABI_PATH, 'utf8', function (err,data) {
32+
if (err) {
33+
return console.log(err);
34+
}
35+
loadContract(data, web3)
36+
});
37+
38+
app.listen(PORT, () => {
39+
console.log(`Listening to port ${PORT}`)
40+
})
41+
app.use(cors({
42+
origin: '*'
43+
}));
44+
}
45+
46+
async function relayGreeting(greetingText, greetingDeadline, greetingSender, v, r, s)
47+
{
48+
const nonce = await web3.eth.getTransactionCount(BACKEND_WALLET_ADDRESS, 'latest'); // nonce starts counting from 0
49+
const transaction = {
50+
'from': BACKEND_WALLET_ADDRESS,
51+
'to': GREETER_CONTRACT_ADDRESS,
52+
'value': 0,
53+
'gas': 300000,
54+
'nonce': nonce,
55+
'data': greeterContract.methods.greet(
56+
[greetingText, greetingDeadline],
57+
greetingSender,
58+
v,
59+
r,
60+
s)
61+
.encodeABI()
62+
};
63+
const { GOERLI_RPC_URL, PRIVATE_KEY } = process.env;
64+
const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
65+
66+
web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) {
67+
if (!error) {
68+
console.log("🎉 The hash of your transaction is: ", hash, "\n");
69+
} else {
70+
console.log("❗Something went wrong while submitting your transaction:", error)
71+
}
72+
});
73+
}
74+
75+
app.get('/relay', (req, res) => {
76+
var greetingText = req.query["greetingText"]
77+
var greetingDeadline = req.query["greetingDeadline"]
78+
var greetingSender = req.query["greetingSender"]
79+
var v = req.query["v"]
80+
var r = req.query["r"]
81+
var s = req.query["s"]
82+
var message = greetingSender + " sent a greet: " + " " + greetingText
83+
relayGreeting(greetingText, greetingDeadline, greetingSender, v, r, s)
84+
res.setHeader('Content-Type', 'application/json');
85+
res.send({
86+
"message": message
87+
})
88+
})
89+
initAPI()

blockchain_stuff.js

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const NETWORK_ID = 5
2+
3+
const GREETER_CONTRACT_ADDRESS = "0x8b4751Bf2Ba59222EB3d40D9fe7FDD98496503b9"
4+
const GREETER_CONTRACT_ABI_PATH = "./json_abi/Greeter.json"
5+
var greeterContract
6+
7+
var accounts
8+
var web3
9+
10+
function metamaskReloadCallback() {
11+
window.ethereum.on('accountsChanged', (accounts) => {
12+
document.getElementById("web3_message").textContent="A message is from web3...";
13+
window.location.reload()
14+
})
15+
window.ethereum.on('networkChanged', (accounts) => {
16+
document.getElementById("web3_message").textContent="A message is from web3...";
17+
window.location.reload()
18+
})
19+
}
20+
21+
const getWeb3 = async () => {
22+
return new Promise((resolve, reject) => {
23+
if(document.readyState=="complete")
24+
{
25+
if (window.ethereum) {
26+
const web3 = new Web3(window.ethereum)
27+
window.location.reload()
28+
resolve(web3)
29+
} else {
30+
reject("must install MetaMask")
31+
document.getElementById("web3_message").textContent="Error: please connect Metamask";
32+
}
33+
}else
34+
{
35+
window.addEventListener("load", async () => {
36+
if (window.ethereum) {
37+
const web3 = new Web3(window.ethereum)
38+
resolve(web3)
39+
} else {
40+
reject("must install MetaMask")
41+
document.getElementById("web3_message").textContent="Error: Please install Metamask";
42+
}
43+
});
44+
}
45+
});
46+
};
47+
48+
const getContract = async (web3, address, abi_path) => {
49+
const response = await fetch(abi_path);
50+
const data = await response.json();
51+
52+
const netId = await web3.eth.net.getId();
53+
contract = new web3.eth.Contract(
54+
data,
55+
address
56+
);
57+
return contract
58+
}
59+
60+
async function loadDapp() {
61+
metamaskReloadCallback()
62+
document.getElementById("web3_message").textContent="Please connect to Metamask"
63+
var awaitWeb3 = async function () {
64+
web3 = await getWeb3()
65+
web3.eth.net.getId((err, netId) => {
66+
if (netId == NETWORK_ID) {
67+
var awaitContract = async function () {
68+
greeterContract = await getContract(web3, GREETER_CONTRACT_ADDRESS, GREETER_CONTRACT_ABI_PATH)
69+
document.getElementById("web3_message").textContent="You are connected to Metamask"
70+
onContractInitCallback()
71+
web3.eth.getAccounts(function(err, _accounts){
72+
accounts = _accounts
73+
if (err != null)
74+
{
75+
console.error("An error occurred: "+err)
76+
} else if (accounts.length > 0)
77+
{
78+
onWalletConnectedCallback()
79+
document.getElementById("account_address").style.display = "block"
80+
} else
81+
{
82+
document.getElementById("connect_button").style.display = "block"
83+
}
84+
});
85+
};
86+
awaitContract();
87+
} else {
88+
document.getElementById("web3_message").textContent="Please connect to Goerli";
89+
}
90+
});
91+
};
92+
awaitWeb3();
93+
}
94+
95+
async function connectWallet() {
96+
await window.ethereum.request({ method: "eth_requestAccounts" })
97+
accounts = await web3.eth.getAccounts()
98+
onWalletConnectedCallback()
99+
}
100+
101+
loadDapp()
102+
103+
const onContractInitCallback = async () => {
104+
var greetingText = await greeterContract.methods.greetingText().call()
105+
var greetingSender = await greeterContract.methods.greetingSender().call()
106+
107+
var contract_state = "Greeting Text: " + greetingText
108+
+ ", Greeting Setter: " + greetingSender
109+
110+
document.getElementById("contract_state").textContent = contract_state;
111+
}
112+
113+
const onWalletConnectedCallback = async () => {
114+
}
115+
116+
// Sign and Relay functions
117+
118+
async function signMessage(message, deadline)
119+
{
120+
const msgParams = JSON.stringify({
121+
types: {
122+
EIP712Domain: [
123+
{ name: 'name', type: 'string' },
124+
{ name: 'version', type: 'string' },
125+
{ name: 'chainId', type: 'uint256' },
126+
{ name: 'verifyingContract', type: 'address' },
127+
],
128+
Greeting: [
129+
{ name: 'text', type: 'string' },
130+
{ name: 'deadline', type: 'uint' }
131+
],
132+
},
133+
primaryType: 'Greeting',
134+
domain: {
135+
name: 'Ether Mail',
136+
version: '1',
137+
chainId: NETWORK_ID,
138+
verifyingContract: GREETER_CONTRACT_ADDRESS,
139+
},
140+
message: {
141+
text: message,
142+
deadline: deadline,
143+
},
144+
});
145+
console.log(msgParams)
146+
147+
const signature = await ethereum.request({
148+
method: "eth_signTypedData_v4",
149+
params: [accounts[0], msgParams],
150+
});
151+
152+
document.getElementById("signature").textContent="Signature: " + signature;
153+
}
154+
155+
async function relayGreeting(greetingText, greetingDeadline, greetingSender, signature)
156+
{
157+
const r = signature.slice(0, 66);
158+
const s = "0x" + signature.slice(66, 130);
159+
const v = parseInt(signature.slice(130, 132), 16);
160+
console.log({v,r,s})
161+
162+
var url = "http://localhost:8080/relay?"
163+
url += "greetingText=" + greetingText
164+
url += "&greetingDeadline=" + greetingDeadline
165+
url += "&greetingSender=" + greetingSender
166+
url += "&v=" + v
167+
url += "&r=" + r
168+
url += "&s=" + s
169+
170+
const relayRequest = new Request(url, {
171+
method: 'GET',
172+
headers: new Headers(),
173+
mode: 'cors',
174+
cache: 'default',
175+
});
176+
177+
fetch(relayRequest);
178+
179+
alert("Message sent!")
180+
}

contracts/Verifier.sol

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.18;
3+
4+
contract Greeter {
5+
string public greetingText = "Hello World!";
6+
address public greetingSender;
7+
8+
struct EIP712Domain {
9+
string name;
10+
string version;
11+
uint256 chainId;
12+
address verifyingContract;
13+
}
14+
15+
struct Greeting {
16+
string text;
17+
uint deadline;
18+
}
19+
20+
bytes32 DOMAIN_SEPARATOR;
21+
22+
constructor () {
23+
DOMAIN_SEPARATOR = hash(EIP712Domain({
24+
name: "Ether Mail",
25+
version: '1',
26+
chainId: block.chainid,
27+
verifyingContract: address(this)
28+
}));
29+
}
30+
31+
function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
32+
return keccak256(abi.encode(
33+
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
34+
keccak256(bytes(eip712Domain.name)),
35+
keccak256(bytes(eip712Domain.version)),
36+
eip712Domain.chainId,
37+
eip712Domain.verifyingContract
38+
));
39+
}
40+
41+
function hash(Greeting memory greeting) internal pure returns (bytes32) {
42+
return keccak256(abi.encode(
43+
keccak256("Greeting(string text,uint deadline)"),
44+
keccak256(bytes(greeting.text)),
45+
greeting.deadline
46+
));
47+
}
48+
49+
function verify(Greeting memory greeting, address sender, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
50+
bytes32 digest = keccak256(abi.encodePacked(
51+
"\x19\x01",
52+
DOMAIN_SEPARATOR,
53+
hash(greeting)
54+
));
55+
return ecrecover(digest, v, r, s) == sender;
56+
}
57+
58+
function greet(Greeting memory greeting, address sender, uint8 v, bytes32 r, bytes32 s) public {
59+
require(verify(greeting, sender, v, r, s), "Invalid signature");
60+
require(block.timestamp <= greeting.deadline, "Deadline reached");
61+
greetingText = greeting.text;
62+
greetingSender = sender;
63+
}
64+
}

0 commit comments

Comments
 (0)