Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/demo/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,7 @@ export const env = cleanEnv(process.env, {
FAUCET_ADDRESS: str({
default: getFaucetAddressDefault(),
}),
SMART_WALLET_IMPL_ADDRESS: str({
default: '0x000100abaad02f1cfc8bbe32bd5a564817339e72',
}),
})
6 changes: 2 additions & 4 deletions packages/demo/backend/src/controllers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,10 @@ export class WalletController {
const {
params: { userId },
} = validation.data
const { privyAddress, smartWalletAddress } =
await walletService.createWallet()
const { walletAddress } = await walletService.createWallet()

return c.json({
privyAddress,
smartWalletAddress,
walletAddress,
userId,
} satisfies CreateWalletResponse)
} catch (error) {
Expand Down
16 changes: 8 additions & 8 deletions packages/demo/backend/src/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import { env } from '@/config/env.js'

import { getVerbs } from '../config/verbs.js'

export async function createWallet(): Promise<{
privyAddress: string
smartWalletAddress: string
}> {
export async function createWallet() {
const verbs = getVerbs()
const privyWallet = await verbs.wallet.privy!.createWallet()
const smartWallet = await verbs.wallet.smartWallet!.createWallet([getAddress(privyWallet.address)])
return { privyAddress: privyWallet.address, smartWalletAddress: smartWallet.address }
// fund the privy wallet with ETH
await fundWallet(privyWallet.walletId, 'ETH')
console.log('delegating privy wallet to smart wallet')
await privyWallet.authorize7702(unichain.id, env.SMART_WALLET_IMPL_ADDRESS as Address);
return { walletAddress: privyWallet.address }
}

export async function getWallet(userId: string): Promise<{
Expand All @@ -49,7 +49,7 @@ export async function getWallet(userId: string): Promise<{
throw new Error('Wallet not found')
}
const wallet = await verbs.wallet.smartWallet.getWallet(
[getAddress(privyWallet.address)],
getAddress(privyWallet.address),
)
return { privyWallet, wallet }
}
Expand All @@ -70,7 +70,7 @@ export async function getAllWallets(
if (!verbs.wallet.smartWallet) {
throw new Error('Smart wallet not configured')
}
return { privyWallet: wallet, wallet: await verbs.wallet.smartWallet.getWallet([getAddress(wallet.address)]) }
return { privyWallet: wallet, wallet: await verbs.wallet.smartWallet.getWallet(getAddress(wallet.address)) }
}),
)
} catch {
Expand Down
3 changes: 1 addition & 2 deletions packages/demo/frontend/src/components/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,7 @@ Active Wallets: 0`,
id: `success-${Date.now()}`,
type: 'success',
content: `Wallet created successfully!
Privy Address: ${result.privyAddress}
Smart Wallet Address: ${result.smartWalletAddress}
Wallet Address: ${result.walletAddress}
User ID: ${result.userId}`,
timestamp: new Date(),
}
Expand Down
4 changes: 1 addition & 3 deletions packages/sdk/src/types/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ export interface GetAllWalletsResponse {
*/
export interface CreateWalletResponse {
/** Wallet address */
privyAddress: string
/** Smart wallet address */
smartWalletAddress: string
walletAddress: string
/** User ID */
userId: string
}
Expand Down
81 changes: 80 additions & 1 deletion packages/sdk/src/wallet/PrivyWallet.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { Address, Hex, Quantity } from 'viem'
import { type Address, type Hash,type PublicClient, type Hex, type Quantity, encodeFunctionData, encodeAbiParameters, http, createWalletClient } from 'viem'

import type { ChainManager } from '@/services/ChainManager.js'

import type { PrivyWalletProvider } from './providers/privy.js'
import { SupportedChainId } from '@/constants/supportedChains.js'
import { smartWalletAbi } from '@/abis/smartWallet.js'
import { unichain } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

/**
* Privy wallet implementation
Expand Down Expand Up @@ -31,6 +35,31 @@ export class PrivyWallet {
this.address = address
}

/**
* Send a signed transaction
* @description Sends a pre-signed transaction to the network
* @param signedTransaction - Signed transaction to send
* @param publicClient - Viem public client to send the transaction
* @returns Promise resolving to transaction hash
*/
async send(
signedTransaction: string,
publicClient: PublicClient,
): Promise<Hash> {
try {
const hash = await publicClient.sendRawTransaction({
serializedTransaction: signedTransaction as `0x${string}`,
})
return hash
} catch (error) {
throw new Error(
`Failed to send transaction: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
)
}
}

/**
* Sign a transaction without sending it
* @description Signs a transaction using Privy's wallet API but doesn't send it
Expand Down Expand Up @@ -83,4 +112,54 @@ export class PrivyWallet {
)
}
}

async authorize7702(chainId: SupportedChainId, contract: Address) {
const authorization = await this.privyProvider.privy.walletApi.ethereum.sign7702Authorization({
walletId: this.walletId,
contract,
chainId,
});
console.log('Authorization signed: %s', authorization)

const initializeData = encodeFunctionData({
abi: smartWalletAbi,
functionName: 'initialize',
args: [[encodeAbiParameters([{ type: 'address' }], [this.address])]],
})
const publicClient = this.chainManager.getPublicClient(chainId);

const nonce = await publicClient.getTransactionCount({
address: this.address,
blockTag: 'pending', // Use pending to get the next nonce including any pending txs
})
const walletClient = createWalletClient({
chain: unichain,
transport: http('http://127.0.0.1:9545'),
account: privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'),
})
const hash = await walletClient.sendTransaction({
// account: this.address,
to: this.address,
data: initializeData,
authorizationList: [{...authorization, address: authorization.contract, chainId: Number(authorization.chainId), nonce: Number(authorization.nonce)}],
value: 0n,
})
// const tx = {
// from: this.address,
// to: this.address,
// data: initializeData,
// authorizationList: [authorization],
// chainId,
// nonce,
// };
// const response = await this.privyProvider.privy.walletApi.ethereum.signTransaction({
// walletId: this.walletId,
// transaction: tx,
// });
console.log('delegated smart wallet hash', hash);
// const hash = await this.send(response.signedTransaction, publicClient);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('delegated smart wallet receipt', receipt);
return hash;
}
}
22 changes: 3 additions & 19 deletions packages/sdk/src/wallet/providers/smartWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,11 @@ export class SmartWalletProvider {
}

async getWallet(
initialOwnerAddresses: Address[],
nonce?: bigint,
currentOwnerAddresses?: Address[],
eoaAddress: Address
): Promise<SmartWallet> {
// Factory is the same accross all chains, so we can use the first chain to get the wallet address
const publicClient = this.chainManager.getPublicClient(
this.chainManager.getSupportedChains()[0],
)
const encodedOwners = initialOwnerAddresses.map((ownerAddress) =>
encodeAbiParameters([{ type: 'address' }], [ownerAddress]),
)
const smartWalletAddress = await publicClient.readContract({
abi: smartWalletFactoryAbi,
address: smartWalletFactoryAddress,
functionName: 'getAddress',
args: [encodedOwners, nonce || 0n],
})
const owners = currentOwnerAddresses || initialOwnerAddresses
return new SmartWallet(
smartWalletAddress,
owners,
eoaAddress,
[eoaAddress],
this.chainManager,
this.lendProvider,
)
Expand Down