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=0x0576a174D229E3cFA37253523E645A78A0C91B57
- 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.
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 excnfig.json
file.
{
// 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.condig.ts
. - Deploy the factory using
INFURA_ID=<required> npx hardhat deploy --network <network>
. - Edit the
factory_address
insrc/exconfig.ts
- Edit the
bundler
url insrc/exconfig.ts
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 infinitism 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 infinitism repo, you can find the instructions below.
- Edit the
bundler
tohttp://localhost:9000/rpc
url insrc/exconfig.ts
that points to your network and accepts requests for your EntryPoint. - 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>;
abstract signUserOpWithContext(
userOp: UserOperationStruct,
context?: 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 defines the component that will be displayed to the user whenever the dapp requests to initiate a transaction, i.e. dapp calls eth_sendTransaction
RPC method. You can display custom information or collect user inputs if needed.
The signature of the TransactionComponent
is defined as follows.
export interface TransactionComponentProps {
transaction: EthersTransactionRequest;
onComplete: (
modifiedTransaction: EthersTransactionRequest,
context?: any
) => Promise<void>;
}
export interface TransactionComponent
extends React.FC<TransactionComponentProps> {}
Once the component has collected enough information from the user, it should pass the collected information to onComplete
as the context
parameter. You can also modify the transaction if you want and return it also as a parameter of onComplete
function. This context
and modifiedTransaction
will be passed on to your createUnsignedUserOp
function of account-api
The signature of the createUnsignedUserOp
is as follows, which shows how the context
will be passed:
/** sign a message for the user */
abstract createUnsignedUserOp: (
request?: MessageSigningRequest,
context?: any
) => Promise<string>;
If you want you can also attach a paymaster here if your wallet wants to sponsor the transaction as well. The paymaster information will be displayed to the user.
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