Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ OP_API_TOKEN=
OP_VAULT_SERVER_HOST=
OP_PRIVATE_KEY_VAULT_ID=
OP_API_TOKEN_VAULT_ID=
OP_ACCESS_TOKEN_VAULT_ID=
OP_CAPSULE_KEY_VAULT_ID=

# Privy - web3 auth
PRIVY_APP_SECRET=
PRIVY_APP_ID=
# Capsule
CAPSULE_API_KEY=

# ⛓️ Blockchains
## Base Sepolia
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@prisma/client": "^5.20.0",
"@react-email/components": "^0.0.25",
"@react-email/tailwind": "0.1.0",
"@thirdweb-dev/sdk": "^4.0.99",
"@types/qrcode": "^1.5.5",
"@usecapsule/server-sdk": "^2.1.0",
"@usecapsule/viem-v2-integration": "^2.1.0",
Expand Down
100 changes: 82 additions & 18 deletions src/app/api/gaslessTxTest/route.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,94 @@
import { NextResponse } from "next/server";
import { StatusCodes } from "http-status-codes";
import { withUserAccessToken } from "@/app/middleware/withUserAccessToken";
import { encodeFunctionData } from "viem";
import { CallWithERC2771Request } from "@gelatonetwork/relay-sdk-viem";
import { activeChain } from "@/lib/viem";
import { gaslessTransaction } from "@/lib/gelato";
import { getCapsuleSigner } from "@/lib/capsule";
import { contractArtifacts, readContract, rpcUrl } from "@/lib/viem";
import { ethers } from "ethers";
import { NonceManager } from "@ethersproject/experimental";
import { withDevAccessToken } from "@/app/middleware/withDevAccessToken";

async function getHandler(req: NextRequestWithUserAccessToken) {
const counterContractAddress = "0x5034F97bf9518Aa191678Eb8E9B202f0Cf1aE3f1";
const counterAbi = [{ "inputs": [], "name": "getCount", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "increment", "outputs": [], "stateMutability": "nonpayable", "type": "function" }];
async function getHandler(req: NextRequestWithDevAccessToken) {
// 🏗️ TODO: clean this - WiP
// const res = await fetch(`https://engine.sketchpad-1.forma.art/relayer/393b72f1-11b4-4d9a-8aa0-2fc19e320501/transaction/status/6914d05f-e199-44c6-b905-742a69096dbc`)
//
// const json = await res.json()
// console.log("🔮 json: ", json)
//
// return
const capsuleSigner = await getCapsuleSigner(req.capsuleTokenVaultKey);

console.log({ capsuleSignerAddress: capsuleSigner.account.address });

const contractAddress = "0x2CE1C37d11971C6A95e408DF3bdf78ef9999DE68";
const abi = contractArtifacts["tickets"].abi;
const data = encodeFunctionData({
abi: counterAbi,
functionName: "increment"
abi,
functionName: "count",
args: []
});
const request: CallWithERC2771Request = {
chainId: BigInt(activeChain.id),
target: counterContractAddress,
data: data as any,
user: req.walletAddress

const owner = await readContract(
contractAddress,
contractArtifacts["tickets"].abi,
"owner",
[]
);
console.log("🔮 owner: ", owner);

const provider = new ethers.providers.JsonRpcProvider({
skipFetchSetup: true,
fetchOptions: {
referrer: process.env.NEXT_PUBLIC_BASE_URL!
},
url: rpcUrl!
});

const signer = provider.getSigner(capsuleSigner.account.address);
const nonceManager = new NonceManager(signer);
const nonce = await nonceManager.getTransactionCount("latest");

const gasEstimate = await provider.estimateGas({
from: capsuleSigner.account.address,
to: contractAddress,
data
});

console.log({ gasEstimate, string: gasEstimate.toString() });

const transaction = {
chainid: process.env.NEXT_PUBLIC_CHAIN_ID,
from: capsuleSigner.account.address,
to: contractAddress,
value: "0",
gas: gasEstimate.toString(),
nonce,
data
};
const gaslessTxResponse = await gaslessTransaction(request, req.capsuleTokenVaultKey);
return NextResponse.json({ gaslessTxResponse }, {

const signature = await capsuleSigner.signMessage(JSON.stringify(transaction));

console.log("🔮 transaction: ", transaction);

const payload = {
type: "forward",
request: transaction,
signature,
forwarderAddress: "0x839320b787DbB268dCF0170302b16b25168B6bA7" // TODO: make it some var, maybe env var?
};

const response = await fetch("relayer-address", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});

const relayerRes = await response.json();
console.log("🌳 relayerRes: ", relayerRes);
return NextResponse.json({}, {
status: StatusCodes.OK
});
}

export const GET = withUserAccessToken(getHandler);

export const GET = withDevAccessToken(getHandler);
2 changes: 1 addition & 1 deletion src/lib/contracts/artifacts/tickets.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/lib/viem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { privateKeyToAccount } from "viem/accounts";

export const rpcUrl = process.env.NEXT_PUBLIC_JSON_RPC_URL || "define RPC URL env ";
export const chainId = Number(process.env.NEXT_PUBLIC_CHAIN_ID) || 84532;
export const ethNativeCurrency = {
export const nativeCurrency = {
decimals: 18,
name: "TIA",
symbol: "TIA"
};
export const baseSepolia = {
export const formaSketchpad = {
id: chainId,
name: "Forma Sketchpad",
nativeCurrency: ethNativeCurrency,
nativeCurrency,
rpcUrls: {
default: {
http: [rpcUrl],
Expand All @@ -28,7 +28,7 @@ export const baseSepolia = {
}
}
};
export const activeChain = baseSepolia;
export const activeChain = formaSketchpad;

export const account = privateKeyToAccount(`0x${process.env.OPERATOR_PRIVATE_KEY}`);

Expand Down
8 changes: 5 additions & 3 deletions src/utils/getExplorerUrl.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export const getExplorerUrl = (param: string, type: "hash" | "contract"): string => {
const baseUrl = "https://explorer.sketchpad-1.forma.art"

export const getExplorerUrlForma = (param: string, type: "hash" | "contract"): string => {
switch (type) {
case "hash":
return `https://sepolia.voyager.online/tx/${param}`;
return `${baseUrl}/tx/${param}`;
case "contract":
return `https://sepolia.voyager.online/contract/${param}`;
return `${baseUrl}/address/${param}`;
}
};