Skip to content

Repository files navigation

RedDuck

Proxy Wallet — Wallet & Marketplace

A browser-extension wallet built to be rented, plus the marketplace that rents it out.


A Yarn monorepo holding the two frontends of the Proxy Wallet project: ProxyWallet, a Manifest V3 Chrome extension wallet, and rent-app, a Next.js marketplace for listing and renting NFTs through it. Both talk to the chainlink-hackathon-contracts SmartWalletV1 / SmartWalletFactoryV1 / NftRent contracts; rent-app additionally reads listing and rental state from chainlink-hackathon-subgraph instead of querying the chain directly.

Built with

Area Technology
Extension Manifest V3, React 18, TypeScript, Webpack 5, Zustand, Radix UI, LocalForage
Marketplace (rent-app) Next.js 14 (Pages Router), wagmi + viem, Apollo Client (subgraph queries), Tailwind CSS
Chain reads/writes ethers v5 + generated Typechain bindings (extension), viem/wagmi (rent-app)
Off-chain data Moralis (NFT ownership), IPFS gateway resolution (NFT metadata)
Tooling Yarn workspaces, ultra-runner, ESLint, Prettier, Husky + commitlint

The mechanisms, and why each exists

Question Mechanism Package
How does a webpage talk to the wallet without the wallet trusting the page? An injected window.proxyWallet.provider object, relayed through a content script to the background service worker extension/src/lib/providers, extension/src/lib/message-bridge
How is the mnemonic / private key protected at rest? AES-encrypted in LocalForage, decrypted only in the background worker behind a session password that never touches storage extension/src/lib/storage, extension/src/Popup/storageUtils
How does the extension turn a mnemonic into a rentable on-chain wallet? Background methods that call SmartWalletFactoryV1.create2Wallet and cache the resulting address per account extension/src/lib/providers/background/methods/internal
How does the marketplace know what's listed and who's renting it, without indexing the chain itself? GraphQL queries against the subgraph via Apollo Client rent-app/src/hooks/queries
How does the marketplace quote a rental's cost in ETH regardless of listing currency? A live read against the UniswapV3Quoter contract rent-app/src/hooks/queries/use-required-native-rent.ts
How does the marketplace get an address to transact with? The same injected-provider request() call any dApp would make rent-app/src/hooks/use-request-user-accounts-proxy-wallet.ts

Architecture

packages/
├── extension/                 # ProxyWallet — Manifest V3 Chrome extension
│   └── src/
│       ├── Background/           # Service worker entry point — message-bridge listener, notifications
│       ├── Content/               # Content script — injects the provider, relays window <-> background
│       ├── Popup/                 # Wallet UI: onboarding, home, send / sign / switch-chain flows
│       ├── Options/ Panel/ Devtools/  # Secondary extension surfaces
│       ├── lib/
│       │   ├── providers/             # Injected EIP-1193-style provider + background RPC method implementations
│       │   ├── message-bridge/        # window <-> content-script <-> background transport
│       │   ├── message-handlers/      # Routes an incoming message to its internal/external handler
│       │   └── storage/               # Encrypted mnemonic/private-key storage
│       └── typechain/             # Generated types for SmartWalletV1 / SmartWalletFactoryV1 / UniswapV3Quoter
└── rent-app/                   # Next.js marketplace — discover, list, and rent NFTs
    └── src/
        ├── components/pages/index/   # Discover / My NFTs / List NFT tabs
        ├── hooks/queries/             # Subgraph (Apollo) and on-chain (wagmi) reads
        ├── constants/                 # Per-chain contract addresses and subgraph endpoints
        └── pages/api/                 # NFT metadata proxy (Moralis + IPFS, with per-network mocks)

Injected-provider request flow

flowchart LR
    dApp["dApp: window.proxyWallet.provider.request()"] --> Inject["inject.ts (Provider)"]
    Inject -->|postMessage| EventBridge[event-bridge.ts]
    EventBridge --> ContentScript[content.ts]
    ContentScript -->|runtime message| Background[Background service worker]
    Background --> Handler[background-message-handler.ts]
    Handler -->|external e.g. eth_sendTransaction| ExternalMethods[methods/external]
    Handler -->|internal e.g. initializeWallet| InternalMethods[methods/internal]
    ExternalMethods --> Popup["Popup UI (user approval)"]
    Popup --> Handler
    Handler --> ContentScript
    ContentScript --> EventBridge
    EventBridge --> Inject
    Inject --> dApp
Loading

The provider sets isMetaMask: true and registers under name: 'ethereum' for compatibility with dApps and libraries that gate wallet features on that flag — it does not claim window.ethereum itself, so it lives alongside other wallets as window.proxyWallet.provider rather than replacing them. External (EIP-1193) methods like eth_sendTransaction route through the Popup for explicit user approval before a background method executes them; internal methods (account/network state, wallet deployment) don't need that round trip.

Renting from the marketplace

flowchart LR
    RentApp[rent-app] -->|GraphQL query| Subgraph[(chainlink-hackathon-subgraph)]
    RentApp -->|price quote| UniswapV3Quoter
    RentApp -->|request accounts| ProxyWallet[ProxyWallet extension]
    ProxyWallet -->|signs & sends| NftRent[NftRent contract]
    NftRent --> SmartWalletFactoryV1
Loading

rent-app never talks to the chain to find out what's listed or rented — it queries the subgraph, the same one described in chainlink-hackathon-subgraph. The only on-chain reads it does directly are price-sensitive ones (the Uniswap quote) that need to be current to the block, not eventually-consistent.

Getting started

nvm install 20 && nvm use 20
npm install --global yarn
yarn install

Extension:

cd packages/extension
cp .env.example .env   # COINGECKO_API_KEY
yarn start

Then in Chrome: remove conflicting wallet extensions, go to chrome://extensions → enable Developer mode → Load unpacked → select packages/extension/build.

Marketplace:

cd packages/rent-app
cp .env.example .env   # MORALIS_API_KEY, NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SEPOLIA_GRAPH_URL
yarn dev

NEXT_PUBLIC_SEPOLIA_GRAPH_URL should point at the subgraph deployed from chainlink-hackathon-subgraph; the contract addresses in packages/rent-app/src/constants/addresses.ts should match whatever chainlink-hackathon-contracts last deployed to that network.

Design notes & trade-offs

  • window.proxyWallet.provider, not window.ethereum. Claiming the global would fight every other installed wallet for it; a namespaced object avoids that at the cost of dApps needing to know to look for it specifically.
  • Approval only on external methods. Internal methods (reading the current account, deploying a wallet from inside the extension's own UI) skip the Popup round-trip since they're already user-initiated from inside the extension; external methods (anything a webpage can call) always require it.
  • The marketplace trusts the subgraph, not the chain, for listings. This is a deliberate latency/consistency trade-off — subgraph indexing lag is preferable to every page load re-scanning NftRent events.
  • Moralis plus a per-network mock for NFT metadata. Not every network the wallet targets has reliable NFT-metadata indexing yet; the mock path in pages/api/get-nft-metadata.ts keeps the UI functional on those networks during development.

License

MIT © RedDuck Limited

About

Browser wallet extension and marketplace for renting NFTs.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages