Integrating with Privy
Creating a custom UI for the demo app (Using Privy)
This section details the core technologies, smart contracts, and SDKs used in the demo so you can create your own custom interface.
Key Libraries and SDKs
@rhinestone/module-sdk
, for interaction with ERC-7579 modules- NOTE: for compatibility reasons, the module version is locked to
0.2.3
- NOTE: for compatibility reasons, the module version is locked to
startale-aa-sdk
instantiate and manage Startale smart accountsviem
for SC interaction from TS@privy-io/react-auth
for interaction with Privy social login features
Smart Contracts on Soneium Minato
# Standard entrypoint v0.7.0 address
ENTRY_POINT_ADDRESS=0x0000000071727De22E5E9d8BAf0edAc6f37da032
# Startale smart contract wallet related contracts
ACCOUNT_RECOVERY_MODULE_ADDRESS=0xA04D053b3C8021e8D5bF641816c42dAA75D8b597
DEFAULT_ECDSA_VALIDATOR_ADDRESS=0xb997E98eB20Aff2a04AfB9e7bDDf0cf02B92f2eB
STARTALE_ACCOUNT_FACTORY_ADDRESS=0xF227EB456F1B0AC51b07f451040ec1c44aB8D1aA
MOCK_ATTESTER_ADDRESS="0xaeD4d8bAa80948d54d33dE041513D30124e1Ae3f"
# ERC-7579 compatible modules
ACCOUNT_RECOVERY_MODULE_ADDRESS=0x29c3e3268e36f14A4D1fEe97DD94EF2F60496a2D
SMART_SESSIONS_MODULE_ADDRESS=0x716BC27e1b904331C58891cC3AB13889127189a7
# Demo contract
DICE_ROLL_LEDGER_ADDRESS=0x298D8873bA2B2879580105b992049201B60c1975
Service Urls
MINATO_RPC=https://rpc.minato.soneium.org
BUNDLER_URL=https://soneium-minato.bundler.scs.startale.com?apikey=[API_KEY]
PAYMASTER_SERVICE_URL=https://paymaster.scs.startale.com/v1?apikey=[API_KEY]
Custom Implementation Steps
1. Wrap your app in a PrivyProvider
PrivyProvider
<PrivyProvider
appId="[YOUR_APP_ID]"
config={{
// Display email and wallet as login methods
loginMethods: ["email", "google", "wallet"],
appearance: {
theme: "light",
accentColor: "#676FFF",
},
// Create embedded wallets for users who don't have a wallet
embeddedWallets: {
createOnLogin: "all-users",
showWalletUIs: false,
},
supportedChains: [soneiumMinato],
defaultChain: soneiumMinato,
}}
>
2. Initialize clients
- Use
viem
to connect to the target chain.
import {createBundlerClient, createPaymasterClient } from "viem/account-abstraction";
import { soneiumMinato } from "viem/chains";
import {
createPublicClient
encodePacked,
encodeAbiParameters,
encodeFunctionData,
getAccountNonce,
} from "viem";
const chain = soneiumMinato;
const publicClient = createPublicClient({
transport: http(MINATO_RPC),
chain,
});
const bundlerClient = createBundlerClient({
client: publicClient,
transport: http(BUNDLER_URL),
});
const paymasterClient = createPaymasterClient({
transport: http(PAYMASTER_SERVICE_URL),
});
3. Create a Startale Smart Account and a client
- Utilize
startale-aa-sdk
to instantiate a smart account. - Use
window.ethereum
provider as a signer - for backend use a different
signer
instance (f.ex.viem
's local wallet) - use
createSmartAccountClient
for further interaction with the account
import { createSmartAccountClient, StartaleAccountClient, StartaleSmartAccount, toStartaleSmartAccount } from "startale-aa-sdk";
// Convert Privy wallet instance into an EthereumProvider instance
const provider = await wallets[0].getEthereumProvider();
const walletClient = createWalletClient({
account: wallets[0].address as `0x${string}`,
chain: soneiumMinato,
transport: custom(provider),
});
// Create a Startale account
const startaleAccountInstance = await toStartaleSmartAccount({
signer: walletClient,
chain: chain,
transport: http(),
index: BigInt(0), // Nonce=index for account instance with same EOA signer as controller
});
const scsContext = { calculateGasLimits: false, policyId: "sudo" };
const startaleAccountClientInstance = createSmartAccountClient({
account: startaleAccountInstance,
transport: http(BUNDLER_URL),
client: publicClient,
paymaster: {
async getPaymasterData(pmDataParams: GetPaymasterDataParameters) {
pmDataParams.paymasterPostOpGasLimit = BigInt(100000);
pmDataParams.paymasterVerificationGasLimit = BigInt(200000);
pmDataParams.verificationGasLimit = BigInt(500000);
const paymasterResponse = await paymasterClient.getPaymasterData(pmDataParams);
return paymasterResponse;
},
async getPaymasterStubData(pmStubDataParams: GetPaymasterDataParameters) {
const paymasterStubResponse =
await paymasterClient.getPaymasterStubData(pmStubDataParams);
return paymasterStubResponse;
},
},
paymasterContext: scsContext
});
:::info
Paymaster actions and userOperation gas estimation can be overridden with custom calculation or passing fixed values by passing the appropriate field into the config object:
userOperation: {
estimateFeesPerGas: async () => {
return {
maxFeePerGas: BigInt(10000000),
maxPriorityFeePerGas: BigInt(10000000),
};
},
}
:::
4. Install Social Recovery Module (Optional)
- Set up recovery guardians using
getSocialRecoveryValidator
from@rhinestone/module-sdk
. - Install it via the
startaleAccountClientInstance.installModule()
function.
const socialRecovery = getSocialRecoveryValidator({
// SET INITIAL CONFIG
threshold: 1,
guardians: [guardianAddress],
});
const installModuleUserOpHash = await startaleAccountClientInstance.installModule({
module: socialRecoveryModule,
});
//Add a new guardian
const calls = [
{
to: ACCOUNT_RECOVERY_MODULE_ADDRESS,
value: BigInt(0),
data: encodeFunctionData({
abi: SocialRecoveryAbi,
functionName: "addGuardian",
args: [guardian],
}),
},
];
const addGuardianUserOpHash = await startaleAccountClientInstance.sendUserOperation({
callData: await startaleAccountClientInstance.account.encodeCalls(calls),
});
// Remove guardian
const SENTINEL_ADDRESS = "0x0000000000000000000000000000000000000001";
const index = guardians.indexOf(guardian);
if (index < 0) {
console.error("Guardian not found in list");
return;
}
const prevGuardian = index === 0 ? SENTINEL_ADDRESS : guardians[index - 1];
await displayGasOutput();
const calls = [
{
to: ACCOUNT_RECOVERY_MODULE_ADDRESS,
value: BigInt(0),
data: encodeFunctionData({
abi: SocialRecoveryAbi,
functionName: "removeGuardian",
args: [prevGuardian, guardian],
}),
},
];
const removeGuardianUserOpHash = await startaleAccountClientInstance.sendUserOperation({
callData: await startaleAccountClientInstance.account.encodeCalls(calls),
});
// Get guardians list
const accountGuardians = await publicClient.readContract({
address: ACCOUNT_RECOVERY_MODULE_ADDRESS,
abi: SocialRecoveryAbi,
functionName: "getGuardians",
args: [startaleAccountClientInstance.account.address],
});
5. Enable Smart Session Module
- Instantiate the session module with
getSmartSessionsValidator
. - Install the module and configure it for executing transactions without signing.
const sessionsModule = getSmartSessionsValidator({});
const opHash = await startaleAccountClientInstance.installModule({
module: sessionsModule,
});
// Check
const isSmartSessionsModuleInstalled = await startaleAccountClientInstance.isModuleInstalled({
module: sessionsModule,
});
6. Create a Session for Transaction Execution
- Define permissions for allowed contract calls (e.g., the dice roll function).
- Enable the session by calling the
enableSessions
function on the Smart Session contract.
const sessionOwner = privateKeyToAccount(ownerKey as `0x${string}`);
const sessionsModule = toSmartSessionsValidator({
account: startaleAccountClientInstance.account,
signer: sessionOwner,
});
const accountSessionClient = startaleAccountClientInstance.extend(smartSessionCreateActions(sessionsModule));
const selector = toFunctionSelector("writeDiceRoll(uint256)");
const sessionRequestedInfo: CreateSessionDataParams[] = [
{
sessionPublicKey: sessionOwner.address, // session key signer
actionPoliciesInfo: [
{
contractAddress: DICE_ROLL_LEDGER_ADDRESS,
functionSelector: selector,
sudo: true,
},
],
},
];
const createSessionsResponse = await accountSessionClient.grantPermission({
sessionRequestedInfo,
});
const sessionData: SessionData = {
granter: startaleAccountClientInstance.account.address,
description: `Session to increment a counter for ${DICE_ROLL_LEDGER_ADDRESS}`,
sessionPublicKey: sessionOwner.address,
moduleData: {
permissionIds: createSessionsResponse.permissionIds,
action: createSessionsResponse.action,
mode: SmartSessionMode.USE,
sessions: createSessionsResponse.sessions,
},
};
7. Send Transactions Using Session Keys
- Sign transactions using a generated session key.
- The app automatically prepares and sends user operations via the Startale account client.
const isEnabled = await isSessionEnabled({
client: startaleAccountClientInstance.account.client as PublicClient,
account: {
type: "erc7579-implementation",
address: startaleAccountClientInstance.account.address,
deployedOnChains: [chain.id],
},
permissionId: activeSession.moduleData.permissionIds[0],
});
const sessionOwner = privateKeyToAccount(ownerKey);
const smartSessionAccountClient = createSmartAccountClient({
account: await toStartaleSmartAccount({
signer: sessionOwner,
accountAddress: activeSession.granter,
chain: chain,
transport: http(),
}),
transport: http(BUNDLER_URL),
client: publicClient,
paymaster: {
async getPaymasterData(pmDataParams: GetPaymasterDataParameters) {
pmDataParams.paymasterPostOpGasLimit = BigInt(100000);
pmDataParams.paymasterVerificationGasLimit = BigInt(200000);
pmDataParams.verificationGasLimit = BigInt(500000);
const paymasterResponse = await paymasterClient.getPaymasterData(pmDataParams);
return paymasterResponse;
},
async getPaymasterStubData(pmStubDataParams: GetPaymasterDataParameters) {
const paymasterStubResponse =
await paymasterClient.getPaymasterStubData(pmStubDataParams);
return paymasterStubResponse;
},
},
paymasterContext: scsContext,
userOperation: {
estimateFeesPerGas: async () => {
return {
maxFeePerGas: BigInt(10000000),
maxPriorityFeePerGas: BigInt(10000000),
};
},
},
mock: true,
});
const usePermissionsModule = toSmartSessionsValidator({
account: smartSessionAccountClient.account,
signer: sessionOwner,
moduleData: activeSession.moduleData,
});
const useSmartSessionAccountClient = smartSessionAccountClient.extend(
smartSessionUseActions(usePermissionsModule),
);
const callData = encodeFunctionData({
abi: DiceRollLedgerAbi,
functionName: "writeDiceRoll",
args: [BigInt(value)],
});
const userOpHash = await useSmartSessionAccountClient.usePermission({
calls: [
{
to: DICE_ROLL_LEDGER_ADDRESS,
data: callData,
},
],
});
Resources
Updated 4 days ago
What’s Next