Trampoline is a chrome extension boilerplate code to showcase your own Smart Contract Wallets with React 18 and Webpack 5 support.
- Verify that your Node.js version is >= 18.12.0.
- Clone this repository.
- Make sure you configure the
provider
insrc/exconfig.ts
to theGoerli
network. - Edit the
bundler
URL pointing toGoerli
network and accepting EntryPoint=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
- Run
yarn install
to install the dependencies. - Run
yarn start
- Load your extension in Chrome by following these steps:
- Go to
chrome://extensions/
- Enable
Developer mode
- Click on
Load unpacked extension
- Select the
build
folder.
- Go to
- Happy hacking.
There are different git branches in this repository showing some potential vulnerabilities that can be exploited by a rogue wallet.
Warning Auto refresh is disabled by default, so you will have to manually refresh the page. If you make changes in background script or account-api, you will also have to refresh the background page. Check instructions on how to do that below.
Warning Logs of all the blockchain interactions are shown in the background script. Do keep it open for faster debugging.
- Open extension's page:
chrome://extensions/
- Find the Trampoline extension, and click Details.
- Check the
Inspect views
area and click onbackground page
to inspect it's logs - To refresh click
cmd + r
orctrl + r
in the background inspect page to refresh the background script. - You can reload the extension completely too, the state is always kept in localstorage so nothing will be lost.
Config of the extension can be set in exconfig.ts
file.
export default {
// Enable or disable password for the user.
enablePasswordEncryption: true,
// Show default transaction screen
showTransactionConfirmationScreen: true,
// stateVersion is the version of state stored in localstorage of your browser. If you want to reset your extension, change this number to a new version and that will invalidate the older state.
stateVersion: "0.1",
// Network that your SCW supports. Currently this app only supports a single network, we will soon have support for multiple networks in future
network: {
chainID: "5",
family: "EVM",
name: "Goerli",
provider: "https://goerli.infura.io/v3/bdabe9d2f9244005af0f566398e648da",
entryPointAddress: "0x0F46c65C17AA6b4102046935F33301f0510B163A",
bundler: "https://app.stackup.sh/api/v1/bundler/96771b1b09e802669c33a3fc50f517f0f514a40da6448e24640ecfd83263d336",
baseAsset: {
symbol: "ETH",
name: "ETH",
decimals: 18,
image: "https://ethereum.org/static/6b935ac0e6194247347855dc3d328e83/6ed5f/eth-diamond-black.webp"
}
}
};
- Make sure EntryPoint is deployed on the target network.
- Edit the
entryPointAddress
insrc/exconfig.ts
. - Add your network details in
hardhat.config.ts
. - Deploy the factory using
INFURA_ID=<required> npx hardhat deploy --network <network>
. - Edit the
factory_address
insrc/exconfig.ts
- Set
bundler
insrc/exconfig.ts
to a bundler that points to your network and accepts requests for your EntryPoint. - Run
yarn start
- Run a local hardhat node with
npx hardhat node
or use the node inside the bundler repo. - Deploy EntryPoint from the account-abstraction repo, you can find the instructions below.
- Edit the
entryPointAddress
insrc/exconfig.ts
. - Deploy the factory using
npx hardhat deploy --network localhost
. - Edit the
factory_address
insrc/exconfig.ts
- Start a local bunder from the bundler repo, you can find the instructions below.
- Set
bundler
tohttp://localhost:3000/rpc
insrc/exconfig.ts
. - Run
yarn start
- Clone the repo https://github.com/eth-infinitism/account-abstraction
- Run
yarn install
to install the dependencies. - Deploy EntryPoint with
DEBUG=true MNEMONIC_FILE=<path-to-mnemonic-file> yarn deploy --network dev
- Clone the repo https://github.com/eth-infinitism/bundler
- Run
yarn install
to install the dependencies. - Run
yarn preprocess
to compile all the local dependencies. - Edit
bundler.config.json
atpackages/bundler/localconfig
: a. Editnetwork
to your local hardhat node b. Edit theentryPoint
address that you got while deploying it using instructions above. c. Make sure your mnemonic & beneficiary are setup correctly. - Run the bunder using
yarn bundler --unsafe --auto
- You can change the icons at
src/assets/img/icon-34.png
andsrc/assets/img/icon-128.png
for the chrome extension.
All your extension's account code must be placed in the src/pages/Account
folder.
There are two subfolders in src/pages/Account
:
- account-api
- components
This folder is used to define the AccountAPI
of your specific account implementation. Every implementation must implement AccountApiType
.
export abstract class AccountApiType extends BaseAccountAPI {
abstract serialize: () => Promise<object>;
/** sign a message for the user */
abstract signMessage: (
request?: MessageSigningRequest,
context?: any
) => Promise<string>;
/**
* Called after the user is presented with the pre-transaction confirmation screen
* The context passed to this method is the same as the one passed to the
* onComplete method of the PreTransactionConfirmationComponent
*/
async createUnsignedUserOpWithContext(
info: TransactionDetailsForUserOp,
preTransactionConfirmationContext?: any
): Promise<UserOperationStruct>;
/**
* Called after the user is presented with the post-transaction confirmation screen
* The context passed to this method is the same as the one passed to the
* onComplete method of the PostTransactionConfirmationComponent
*/
abstract signUserOpWithContext(
userOp: UserOperationStruct,
postTransactionConfirmationContext?: any
): Promise<string>;
}
export declare abstract class BaseAccountAPI {
/**
* return the value to put into the "initCode" field, if the contract is not yet deployed.
* this value holds the "factory" address, followed by this account's information
*/
abstract getAccountInitCode(): Promise<string>;
/**
* return current account's nonce.
*/
abstract getNonce(): Promise<BigNumber>;
/**
* encode the call from entryPoint through our account to the target contract.
* @param target
* @param value
* @param data
*/
abstract encodeExecute(
target: string,
value: BigNumberish,
data: string
): Promise<string>;
The boilerplate includes a SimpleAccount Implementation by Eth-Infinitism, which you can find here.
This folder is used to define the components that will be used in the Chrome extension. This folder should contain two subfolders.
- onboarding
- sign-message
- transaction
The onboarding
folder defines the component that will be displayed to the user during the creation of a new wallet. You can display custom information or collect user inputs if needed.
The signature of the OnboardingComponent
is defined as follows.
export interface OnboardingComponentProps {
onOnboardingComplete: (context?: any) => void;
}
export interface OnboardingComponent
extends React.FC<OnboardingComponentProps> {}
Once the component has collected enough information from the user, it should pass the collected information to onOnboardingComplete
as the context
parameter. This context
will be passed on to your account-api
The signature of the account-api
is as follows, which shows how the context
will be passed:
export interface AccountApiParamsType extends BaseApiParams {
context?: any;
}
export type AccountImplementationType = new (
params: AccountApiParamsType
) => AccountApiType;
The sign-message
folder defines the component that will be displayed to the user whenever the dapp requests the user to sign any message, i.e. dapp calls personal_sign
RPC method. You can display custom information or collect user inputs if needed.
The signature of the SignMessageComponenet
is defined as follows.
export interface SignMessageComponenetProps {
onComplete: (context?: any) => Promise<void>;
}
export interface SignMessageComponenet
extends React.FC<SignMessageComponenetProps> {}
Once the component has collected enough information from the user, it should pass the collected information to onComplete
as the context
parameter. This context
will be passed on to your signMessage
function of account-api
The signature of the signMessage
is as follows, which shows how the context
will be passed:
/** sign a message for the user */
abstract signMessage: (
request?: MessageSigningRequest,
context?: any
) => Promise<string>;
The transaction
folder contains components that are displayed to the user whenever the Dapp requests to initiate a transaction by calling the eth_sendTransaction
RPC method. These components can display custom information or gather necessary user inputs.
There are three key components involved in the transaction process:
PreTransactionConfirmationComponent
TransactionConfirmationComponent
PostTransactionConfirmationComponent
The flow of these components' mounting and interaction is detailed below:
-
Dapp Initiates Transaction: The Dapp calls
eth_sendTransaction
. -
Pre-Transaction Confirmation: The
PreTransactionConfirmationComponent
is loaded. It can display any necessary information and also return acontext
. -
Unsigned User Operation Creation: The
context
from the previous step is passed to the backgroundaccount-api
functioncreateUnsignedUserOpWithContext
. This function returns the unsigned user operation with paymaster and data. If the developer requires any specific parameters for the paymaster, they can be included in thecontext
from Step 2, which is passed tocreateUnsignedUserOpWithContext
. -
Transaction Confirmation: The unsigned user operation is passed to the
TransactionConfirmationComponent
. This is the default transaction confirmation screen, but developers can now modify it as it is part of theaccount-api
components. This component is also passed thecontext
from Step 2 and can return a newcontext
. -
Post-Transaction Confirmation: The
PostTransactionConfirmationComponent
is then mounted with thecontext
from Step 4. This is the stage where developers can request external signs after the transaction has been confirmed by the user (for example, in a two-owner setup, a rainbow is needed). This component also returns acontext
which will be passed toaccount-api
. -
User Operation Signature: After Step 5,
account-api
is called and the functionsignUserOpWithContext
is executed, where thecontext
from Step 5 is passed. -
User Operation Dispatch: Once a signature is received from Step 6, the
userOp
is sent to the blockchain.
The signature of the TransactionComponents
is defined as follows.
export interface TransactionComponentProps {
transaction: EthersTransactionRequest;
onReject: () => Promise<void>;
}
export interface PreTransactionConfirmationtProps
extends TransactionComponentProps {
onComplete: (
modifiedTransaction: EthersTransactionRequest,
context?: any
) => Promise<void>;
}
export interface TransactionConfirmationtProps
extends TransactionComponentProps {
userOp: UserOperationStruct;
context: any;
onComplete: (context?: any) => Promise<void>;
}
export interface PostTransactionConfirmationtProps
extends TransactionComponentProps {
userOp: UserOperationStruct;
context: any;
onComplete: (context?: any) => Promise<void>;
}
export interface PreTransactionConfirmation
extends React.FC<PreTransactionConfirmationtProps> {}
export interface TransactionConfirmation
extends React.FC<TransactionConfirmationtProps> {}
export interface PostTransactionConfirmation
extends React.FC<PostTransactionConfirmationtProps> {}
No you can disable that by setting enablePasswordEncryption
flag to false
in exconfig.ts
.
Warning: the local storage will be unencrypted and your wallet must return an encrypted state when
serialize
function ofaccount-api
willo be called or else the user's fund will be at risk.
If you want to show a custom screen then you must present it to the user in TransactionComponent
and set showTransactionConfirmationScreen
to false
.
You must return the paymaster information in the userOp
constructed by the function createUnsignedUserOp
.
Warning: If
showTransactionConfirmationScreen
has been disabled then the user will not be aware of paymaster and you must inform the user about paymaster in your custom transaction confirmation screen.
This repository is based on the boilerplate code found at lxieyang/chrome-extension-boilerplate-react. To understand how hot-reloading and content scripts work, refer to its README.
Designed by Tomo Saito, a designer and artist at the Ethereum Foundation. @tomosaito