SketchVault
Offer painters a gasless, onchain-secured vault for storing and sharing preliminary sketches with social login.
Privy social + sponsored tx· wallet UX
Section · Onchain
full primer →The primitive.
Painters sign in with Google through Privy — no seed phrase, no MetaMask popup — and their secure sketch storage actions are sent as sponsored transactions so they never see gas.
Why this primitivePrivy wallet plus sponsored tx enable secure, cost-free storage of sensitive creative drafts.
Kernel
Privy embedded wallet bootstrapped with PRIVY_APP_ID — Google sign-in — with a ZKsync paymaster contract paying gas so users never see a fee
Drives the UI as
a one-click 'Sign in with Google' that drops the user straight into the app with an onchain identity
Required keys.
ZKSYNC_SEPOLIA_RPC_URL
Alchemy ZKsync Sepolia HTTPS endpoint. Create a free app → copy the HTTPS URL.
open ↗Add these in your Lovable project under Settings → Secrets before pasting the prompt below.
Appendix · Mega-prompt
The build prompt.
budget · 1 message
Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →
Build "SketchVault" in ONE Lovable message. Single-page demo on ZKsync Sepolia.
CONCEPT
Offer painters a gasless, onchain-secured vault for storing and sharing preliminary sketches with social login.
Discipline: Visual Art (secure sketch storage).
Onchain primitive: Privy social + sponsored tx. Why this primitive: Privy wallet plus sponsored tx enable secure, cost-free storage of sensitive creative drafts.
5-CREDIT BUDGET (HARD LIMIT):
- ONE single-page app. No router, no Lovable Cloud, no database, no auth flows beyond Privy drop-in.
- ONE Solidity contract (plus a shared paymaster) compiled with zksolc, deployed to ZKsync Sepolia (chain id 300), verified via the matterlabs verifier.
- Privy is always the auth layer (Google login, embedded wallet). Gas is paid by a ZKsync paymaster — the user never sees a fee.
- Pinata/IPFS only if the idea genuinely needs to store a file or metadata.
- At most ONE AI call per user action (use Lovable AI Gateway with LOVABLE_API_KEY if AI is part of the idea).
- Skip tests, skip CI, skip docs pages. Ship the demo, nothing else.
STACK
- React + Vite single page (the index route).
- SSR-safe Privy mount is mandatory. Never import @privy-io/react-auth at
module scope of a route file — it crashes SSR. Use
lazy(() => import('./privy-client-entry')) inside <ClientOnly> + <Suspense>,
and put <PrivyProvider> only inside privy-client-entry.tsx.
- PrivyProvider config (chainId is passed per-call; do NOT set defaultChain):
<PrivyProvider appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{ loginMethods:['google','email'],
embeddedWallets:{ ethereum:{ createOnLogin:'users-without-wallets' } },
appearance:{ theme:'dark' } }}>
- Read the embedded wallet from useWallets, not user.wallet:
const embedded = wallets.find(w => w.walletClientType === 'privy');
- CRITICAL: ZKsync gas is paid by the paymaster contract, NOT Privy. Do NOT
pass `sponsor: true` (that is Ethereum-only). Do NOT use
`useSendTransaction` — it emits a plain EVM tx and silently drops the
ZKsync paymaster field, so the user ends up paying gas. Instead, build
the ZKsync EIP-712 tx type 113 with `zksync-ethers`, sign the raw
digest through Privy `useSignRawHash`, and broadcast via a
`zksync-ethers` Provider.
- Send flow (privy-client-entry.tsx):
import { Provider, utils, types, EIP712Signer, Transaction } from "zksync-ethers";
const provider = new Provider(import.meta.env.VITE_ZKSYNC_SEPOLIA_RPC_URL
|| "https://sepolia.era.zksync.dev");
const paymasterParams = utils.getPaymasterParams(PAYMASTER_ADDRESS, {
type: "General", innerInput: new Uint8Array(),
});
const tx: types.TransactionRequest = {
to: CONTRACT_ADDRESS,
from: embedded.address,
data,
chainId: 300,
type: utils.EIP712_TX_TYPE,
customData: { gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, paymasterParams },
value: 0n,
nonce: await provider.getTransactionCount(embedded.address),
gasPrice: await provider.getGasPrice(),
};
tx.gasLimit = await provider.estimateGas(tx);
const signer = new EIP712Signer(
{ getAddress: async () => embedded.address, signTypedData: async () => "0x" } as any, 300);
const digest = await Transaction.from(tx).getSignedDigest?.() ?? await signer.getSignedDigest(tx as any);
const { signature } = await signRawHash({ address: embedded.address, hash: digest });
const populated = { ...tx, customData: { ...tx.customData, customSignature: signature } };
const receipt = await provider.broadcastTransaction(utils.serialize(populated as any));
- Wrap the whole send in a 45s Promise.race timeout whose reject message
names the exact operational cause:
new Error("ZKsync paymaster tx timed out after 45s. Check paymaster is funded (>= 0.01 ETH) and RPC is reachable.")
- DASHBOARD PREREQUISITE (one-time): none for ZKsync — sponsorship lives
in the paymaster contract, not the Privy dashboard.
- Hardhat in /contracts (kept outside the Vite bundle). Install
`@matterlabs/hardhat-zksync` (single meta-package pulling in
hardhat-zksync-solc, hardhat-zksync-deploy, hardhat-zksync-verify at
matched versions) and `@matterlabs/zksync-contracts`.
- hardhat.config.cjs:
require("@matterlabs/hardhat-zksync");
module.exports = {
zksolc: { version: "latest", settings: {} },
solidity: { version: "0.8.24" },
defaultNetwork: "zkSyncSepoliaTestnet",
networks: {
zkSyncSepoliaTestnet: {
url: process.env.ZKSYNC_SEPOLIA_RPC_URL || "https://sepolia.era.zksync.dev",
ethNetwork: "sepolia",
zksync: true,
chainId: 300,
verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification",
accounts: [process.env.METAMASK_PRIVATE_KEY.startsWith("0x")
? process.env.METAMASK_PRIVATE_KEY : "0x" + process.env.METAMASK_PRIVATE_KEY],
},
},
etherscan: { apiKey: process.env.ETHERSCAN_API_KEY },
};
- Deploy scripts use `hre.deployer.deploy(Artifact, [args])` (from
hardhat-zksync-deploy) — NOT `ethers.getContractFactory`. Deploy the
concept contract first, then the GaslessPaymaster with the concept
address as its target. Immediately fund the paymaster:
await deployer.zkWallet.sendTransaction({ to: paymaster.address,
value: ethers.parseEther("0.05") });
- Verify (matterlabs verifier, single command per contract):
`npx hardhat verify --network zkSyncSepoliaTestnet <address> <constructor args>`
On success the source becomes readable at
`https://sepolia.explorer.zksync.io/address/<address>#contract`.
- Frontend reads: use a zksync-ethers Provider — never viem's `sepolia`
chain object, which points at ZKsync Sepolia (chain 11155111), not
ZKsync Sepolia (chain 300).
- Write both addresses to `src/data/contract.json`:
{ "address": "<concept>", "paymaster": "<paymaster>", "chainId": 300,
"network": "ZKsync Sepolia",
"explorer": "https://sepolia.explorer.zksync.io",
"rpc": "https://sepolia.era.zksync.dev" }
CONTRACT (contracts/SketchVault.sol):
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title SocialLogSketchVault
/// @notice Offer painters a gasless, onchain-secured vault for storing and sharing preliminary sketches with social login.
/// @notice Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
contract SocialLogSketchVault {
event Logged(address indexed author, string cid, uint256 at);
/// @notice Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
function log(string calldata cid) external {
emit Logged(msg.sender, cid, block.timestamp);
}
}
```
PAYMASTER (contracts/GaslessPaymaster.sol):
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol";
import { IPaymasterFlow } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol";
import { TransactionHelper, Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";
import { BOOTLOADER_FORMAL_ADDRESS } from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
/// @notice Funded paymaster; pays gas for any tx targeting `target`.
contract GaslessPaymaster is IPaymaster {
address public immutable target;
address public immutable owner;
modifier onlyBootloader() { require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "!bootloader"); _; }
constructor(address _target) { target = _target; owner = msg.sender; }
receive() external payable {}
function validateAndPayForPaymasterTransaction(bytes32, bytes32, Transaction calldata _tx)
external payable onlyBootloader returns (bytes4 magic, bytes memory)
{
magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;
require(address(uint160(_tx.to)) == target, "paymaster: wrong target");
require(_tx.paymasterInput.length >= 4, "paymasterInput too short");
bytes4 sel = bytes4(_tx.paymasterInput[0:4]);
require(sel == IPaymasterFlow.general.selector, "unsupported flow");
uint256 required = _tx.gasLimit * _tx.maxFeePerGas;
(bool ok,) = payable(BOOTLOADER_FORMAL_ADDRESS).call{value: required}("");
require(ok, "paymaster: pay bootloader failed");
}
function postTransaction(bytes calldata, Transaction calldata, bytes32, bytes32, ExecutionResult, uint256)
external payable onlyBootloader {}
function withdraw(address payable to) external { require(msg.sender == owner, "!owner"); to.transfer(address(this).balance); }
}
```
USER FLOW
1. Land on page -> 'Sign in with Google' (Privy) -> embedded wallet auto-provisioned on ZKsync Sepolia.
2. Every secure sketch storage action the user performs is sent as a paymaster-sponsored ZKsync tx (`log(payload)`) and displayed with a ZKsync explorer link. No wallet popups, no gas fee.
3. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14"
REQUIRED SECRETS (Lovable -> Project Settings -> Secrets):
- METAMASK_PRIVATE_KEY ZKsync Sepolia deployer key. Fund via https://faucet.chainstack.com/zksync-testnet-faucet or bridge Sepolia ETH at https://portal.zksync.io/bridge/?network=sepolia
- ZKSYNC_SEPOLIA_RPC_URL Optional; defaults to https://sepolia.era.zksync.dev (public RPC works for hackathons; use a dedicated provider for reliability).
- ETHERSCAN_API_KEY Required by hardhat config plumbing; the actual verify request routes to ZKsync's matterlabs verifier, not Etherscan.
- PRIVY_APP_ID Google/email sign-in + embedded wallet. Docs: https://docs.privy.io/llms-full.txt
- PINATA_JWT IPFS uploads (only if the app pins media). Docs: https://docs.pinata.cloud/llms-full.txt
CREDIT (must appear in UI footer AND as NatSpec on every deployed contract):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Market sizing.
TAM
$700M
secure digital art storage
SAM
$200M
sketch archival platforms
SOM
$12M
gasless storage adopters
Indicative figures for hackathon pitches — refine with your own research before raising.
Adjacent entries.
collaborative painting
Canvas Collaborate
Enable painters to co-create artworks with seamless onchain identity and gasless contributions.
illustrator communityIllustrator Guild
Create a hosted network for illustrators to share, sell, and verify work with easy login and zero gas fees.
generative artGenerative Canvas
Let generative artists deploy and monetize code-driven art with instant gasless onboarding and transactions.
art provenance trackingGallery Ledger
Provide gallerists with gasless, onchain tools to prove artwork provenance and authenticity effortlessly.