Skip to content

Commit

Permalink
Add backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor Stadnyk committed Jul 14, 2024
1 parent d82bf9a commit dd81893
Show file tree
Hide file tree
Showing 8 changed files with 1,613 additions and 0 deletions.
6 changes: 6 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ACCOUNT_PK=
ENDPOINT_URL=https://api.pimlico.io/v2/sepolia/rpc?apikey=<API_KEY>
RPC_URL=https://rpc.ankr.com/eth_sepolia
SIMPLE_ACCOUNT_FACTORY_ADDRESS=
MODULE_ADDRESS=
SIGNATURE=
14 changes: 14 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Hardhat files
cache
artifacts


dist
.env
coverage
coverage.json
typechain
typechain-types
*.d.ts
.npmrc
.DS_Store
1,197 changes: 1,197 additions & 0 deletions backend/abi.json

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions backend/clients.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createClient, createPublicClient, createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { bundlerActions, ENTRYPOINT_ADDRESS_V07 } from "permissionless";
import { pimlicoBundlerActions, pimlicoPaymasterActions } from "permissionless/actions/pimlico.js";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.ACCOUNT_PK)

const walletClient = createWalletClient({
account,
chain: sepolia,
transport: http(process.env.RPC_UR),
})

const publicClient = createPublicClient({
chain: sepolia,
transport: http(process.env.RPC_UR),
})

const bundlerClient = createClient({
transport: http(process.env.ENDPOINT_URL),
chain: sepolia,
})
.extend(bundlerActions(ENTRYPOINT_ADDRESS_V07))
.extend(pimlicoBundlerActions(ENTRYPOINT_ADDRESS_V07))


const paymasterClient = createClient({
transport: http(process.env.ENDPOINT_UR),
chain: sepolia,
}).extend(pimlicoPaymasterActions(ENTRYPOINT_ADDRESS_V07))
99 changes: 99 additions & 0 deletions backend/executor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { encodeFunctionData } from "viem";
import { ENTRYPOINT_ADDRESS_V07, getSenderAddress, signUserOperationHashWithECDSA } from "permissionless";
import { sepolia } from "viem/chains";
import { bundlerClient, paymasterClient, walletClient } from "./clients.js"

export async function execBackup({ owner, name}) {
const factory = process.env.SIMPLE_ACCOUNT_FACTORY_ADDRESSS
const factoryData = encodeFunctionData({
abi: [
{
inputs: [
{ name: "owner", type: "address" },
{ name: "salt", type: "uint256" },
],
name: "createAccount",
outputs: [{ name: "ret", type: "address" }],
stateMutability: "nonpayable",
type: "function",
},
],
args: [walletClient.account.address, 0n],
})

const senderAddress = await getSenderAddress(publicClient, {
factory,
factoryData,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})

const execBackupCallData = encodeFunctionData({
address: process.env.MOUDLE_ADDRESS,
abi,
functionName: "executeBackup",
args: [owner, name]
});

const callData = encodeFunctionData({
abi: [{
inputs: [
{ name: "dest", type: "address" },
{ name: "value", type: "uint256" },
{ name: "func", type: "bytes" },
],
name: "execute",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
args: [process.env.MOUDLE_ADDRESS, 0, execBackupCallData],
})


const gasPrice = await bundlerClient.getUserOperationGasPrice()

const userOperation = {
sender: senderAddress,
nonce: 0n,
factory: factory,
factoryData,
callData,
maxFeePerGas: gasPrice.fast.maxFeePerGas,
maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas,
// dummy signature, needs to be there so the SimpleAccount doesn't immediately revert because of invalid signature length
signature: process.env.signature,
}

const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({
userOperation,
})

const sponsoredUserOperation = {
...userOperation,
...sponsorUserOperationResult,
}


const signature = await signUserOperationHashWithECDSA({
account: walletClient.account,
userOperation: sponsoredUserOperation,
chainId: sepolia.id,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
sponsoredUserOperation.signature = signature


const userOperationHash = await bundlerClient.sendUserOperation({
userOperation: sponsoredUserOperation,
})

try {
const receipt = await bundlerClient.waitForUserOperationReceipt({
hash: userOperationHash,
})
const txHash = receipt.receipt.transactionHash
} catch (e) {

}
}
85 changes: 85 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import fs from 'fs';
import { publicClient } from './clients.js';
import { execBackup } from './executor.js';
import { CronJob } from 'cron';
import dotenv from 'dotenv';

// Load environment variables
dotenv.config();

export const abi = JSON.parse(fs.readFileSync('./abi.json', 'utf8')).abi;

async function getAllUsers() {
let users = [];
let index = 0;

while (true) {
try {
const user = await publicClient.readContract({
address: process.env.MODULE_ADDRESS,
abi,
functionName: 'users',
args: [index],
});

console.log('USER', user);
users.push(user);
index++;
} catch (e) {
console.log('No more users found, stopping search.');
break;
}
}

return users;
}

async function execBackups() {
try {
const users = await getAllUsers();
console.log('Users:', users);

for (const user of users) {
try {
const userBackupsNames = await publicClient.readContract({
address: process.env.MODULE_ADDRESS,
abi,
functionName: 'getBackups',
args: [user],
});

for (const backupName of userBackupsNames) {
try {
const fullBackup = await publicClient.readContract({
address: process.env.MODULE_ADDRESS,
abi,
functionName: 'getBackup',
args: [user, backupName],
});

if (fullBackup.unlockAt <= Math.floor(Date.now() / 1000)) {
console.log(`Executing backup for user ${user} with name ${backupName}`);
await execBackup({ owner: user, name: backupName });
}
} catch (e) {
console.error(`Error fetching or executing backup for user ${user} and backup ${backupName}:`, e);
if (e.name !== 'WaitForUserOperationReceiptTimeoutError') {
throw e;
}
}
}
} catch (e) {
console.error(`Error fetching backups for user ${user}:`, e);
}
}
} catch (e) {
console.error('Error during execBackups:', e);
process.exit(1); // Terminate process with error code
}
}

new CronJob('*/30 * * * *', execBackups).start();

execBackups()
.then((res) => console.log('Initial execution complete:', res))
.catch((err) => console.error('Error during initial execution:', err));
19 changes: 19 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "test-app",
"version": "1.0.0",
"description": "",
"main": "smart-account.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "bun run index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@rhinestone/module-sdk": "^0.1.2",
"cron": "^3.1.7",
"permissionless": "^0.1.39",
"viem": "^2.17.3"
}
}
Loading

0 comments on commit dd81893

Please sign in to comment.