Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/safe-passkey #23

Merged
merged 2 commits into from
Oct 11, 2024
Merged
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 0.1.4

* add surpport for web3_signers v0.1+
* add create safe account with passkeys method in accounts factory
* modify getSafeSignature to support hybrid signatures (private key + passkey)
* update default singleton to safeL2
* refactor safe inititializer
* fix issue that causes create2salt to result in a different safe address
* fetch the smart account balance from the entrypoint
* fix issue where empty amounts array throws a light account ArrayLengthMismatch() error during batch transactions

## 0.1.3

* replace Simple account with Alchemy light account
* hardcoded safe proxy creation code to reduce network requests
* add light account abi and signature prefix
* separate constants from chains

## 0.1.2

* Update web3-signers
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ In order to create a smart wallet client you need to set up a signer, which will

> You have to use the correct signer for the type of account you want to create.

1. `PrivateKeys` - use with simple accounts and safe accounts only
1. `PrivateKeys` - use with light accounts and safe accounts only
2. `Passkey` - use with p256 smart accounts and safe Passkey accounts only
3. `EOA Wallet (Seed Phrases)` - use with simple smart accounts and safe accounts only
3. `EOA Wallet (Seed Phrases)` - use with light smart accounts and safe accounts only
4. `HardWare Signers (Secure Enclave/Keystore)` - use with p256 smart accounts only

### Smart Wallet Factory
Expand All @@ -88,11 +88,11 @@ The smart wallet factory handles the creation of smart wallet instances. Make su
final SmartWalletFactory smartWalletFactory = SmartWalletFactory(chain, signer);
```

#### To Create a Simple Smart Account
#### To Create an Alchemy Light Account

```dart
final Smartwallet wallet = await smartWalletFactory.createSimpleAccount(salt);
print("simple wallet address: ${wallet.address.hex}");
final Smartwallet wallet = await smartWalletFactory.createAlchemyLightAccount(salt);
print("light account wallet address: ${wallet.address.hex}");
```

#### To create a P256 Smart Account (Secure Enclave/Keystore)
Expand Down
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '2.0.20'
repositories {
google()
mavenCentral()
Expand Down
3 changes: 3 additions & 0 deletions example/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
113 changes: 38 additions & 75 deletions example/lib/providers/wallet_provider.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:web3_signers/web3_signers.dart';
import 'package:variance_dart/variance_dart.dart';
import 'package:web3dart/credentials.dart';
import 'package:web3dart/web3dart.dart' as w3d;
import 'package:web3dart/crypto.dart' as w3d;

class WalletProvider extends ChangeNotifier {
final Chain _chain;
Expand All @@ -23,70 +20,59 @@ class WalletProvider extends ChangeNotifier {
final EthereumAddress erc20 =
EthereumAddress.fromHex("0xAEaF19097D8a8da728438D6B57edd9Bc5DAc4795");
final EthereumAddress deployer =
EthereumAddress.fromHex("0x218F6Bbc32Ef28F547A67c70AbCF8c2ea3b468BA");
EthereumAddress.fromHex("0xf5bb7f874d8e3f41821175c0aa9910d30d10e193");

final salt = Uint256.zero;
static const rpc =
"https://api.pimlico.io/v2/84532/rpc?apikey=pim_NuuL4a9tBdyfoogF5LtP5A";

WalletProvider()
: _chain = Chain(
chainId: 31337,
explorer: "https://sepolia.etherscan.io/",
entrypoint: EntryPointAddress(
0.6,
EthereumAddress.fromHex(
"0x5165c9e79213e2208947589c6e1dcc80ee8d3d00")))
..accountFactory = EthereumAddress.fromHex(
"0x0ce83Bf5d20c539E77e1E607B8349E26c6b20133") // v07 p256 factory address
..jsonRpcUrl = "http://127.0.0.1:8545"
..bundlerUrl = "http://localhost:3000/rpc";
// ..paymasterUrl =
// "https://api.pimlico.io/v2/11155111/rpc?apikey=875f3458-a37c-4187-8ac5-d08bbfa0d501";

// "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070" v06 p256 factory address
: _chain = Chains.getChain(Network.baseTestnet)
..accountFactory = Constants.lightAccountFactoryAddressv07
..bundlerUrl = rpc
..paymasterUrl = rpc;

Future<void> registerWithPassKey(String name,
{bool? requiresUserVerification}) async {
final pkpSigner =
PassKeySigner("webauthn.io", "webauthn", "https://webauthn.io");
final hwdSigner = HardwareSigner.withTag(name);
_chain.accountFactory = Constants.safeProxyFactoryAddress;

final salt = Uint256.zero;
Uint256.fromHex(
hexlify(w3d.keccak256(Uint8List.fromList(utf8.encode(name)))));
final options = PassKeysOptions(
name: "variance",
namespace: "variance.space",
origin: "https://variance.space",
userVerification: "required",
requireResidentKey: true,
sharedWebauthnSigner: EthereumAddress.fromHex(
"0xfD90FAd33ee8b58f32c00aceEad1358e4AFC23f9"));
final pkpSigner = PassKeySigner(options: options);

try {
// uses passkeys on android, secure enclave on iOS
if (Platform.isAndroid) {
final SmartWalletFactory walletFactory =
SmartWalletFactory(_chain, pkpSigner);
final keypair = await pkpSigner.register(name, name);
_wallet =
await walletFactory.createP256Account<PassKeyPair>(keypair, salt);
} else if (Platform.isIOS) {
final SmartWalletFactory walletFactory =
SmartWalletFactory(_chain, hwdSigner);
final keypair = await hwdSigner.generateKeyPair();
_wallet = await walletFactory.createP256Account<P256Credential>(
keypair, salt);
}
final SmartWalletFactory walletFactory =
SmartWalletFactory(_chain, pkpSigner);
final keypair = await pkpSigner.register(
"${DateTime.timestamp().millisecondsSinceEpoch}@variance.space",
name);
_wallet = await walletFactory.createSafeAccountWithPasskey(
keypair, salt, options.sharedWebauthnSigner);

log("wallet created ${_wallet?.address.hex} ");
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
log("something happened: $e");
rethrow;
}
}

Future<void> createEOAWallet() async {
_chain.accountFactory = Constants.simpleAccountFactoryAddressv06;

final signer = EOAWallet.createWallet();
final signer = EOAWallet.createWallet(
WordLength.word_12, const SignatureOptions(prefix: [0]));
log("signer: ${signer.getAddress()}");

final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer);
final salt = Uint256.fromHex(hexlify(w3d
.keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes)));

try {
_wallet = await walletFactory.createSimpleAccount(salt);
_wallet = await walletFactory.createAlchemyLightAccount(salt);
log("wallet created ${_wallet?.address.hex} ");
} catch (e) {
_errorMessage = e.toString();
Expand All @@ -99,19 +85,15 @@ class WalletProvider extends ChangeNotifier {
final random = math.Random.secure();
final privateKey = EthPrivateKey.createRandom(random);

final signer = PrivateKeySigner.create(privateKey, "123456", random);
final signer = PrivateKeySigner.create(privateKey, "123456", random,
options: const SignatureOptions(prefix: [0]));
log("signer: ${signer.getAddress()}");
log("pk: ${hexlify(privateKey.privateKey)}");

final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer);

final salt = Uint256.fromHex(hexlify(w3d
.keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes)));

log("pk salt: ${salt.toHex()}");

try {
_wallet = await walletFactory.createSimpleAccount(salt);
_wallet = await walletFactory.createAlchemyLightAccount(salt);
log("pk wallet created ${_wallet?.address.hex} ");
} catch (e) {
_errorMessage = e.toString();
Expand All @@ -128,11 +110,6 @@ class WalletProvider extends ChangeNotifier {

final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer);

final salt = Uint256.fromHex(hexlify(w3d
.keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes)));

log("salt: ${salt.toHex()}");

try {
_wallet = await walletFactory.createSafeAccount(salt);
log("safe created ${_wallet?.address.hex} ");
Expand Down Expand Up @@ -180,30 +157,16 @@ class WalletProvider extends ChangeNotifier {

Future<void> sendTransaction(String recipient, String amount) async {
if (_wallet != null) {
final response = await transferToken(
EthereumAddress.fromHex(recipient),
w3d.EtherAmount.fromBigInt(
w3d.EtherUnit.wei, BigInt.from(20 * math.pow(10, 6))));

// final etherAmount = w3d.EtherAmount.fromBigInt(w3d.EtherUnit.wei,
// BigInt.from(double.parse(amount) * math.pow(10, 18)));
final etherAmount = w3d.EtherAmount.fromBigInt(w3d.EtherUnit.wei,
BigInt.from(double.parse(amount) * math.pow(10, 18)));

// final response =
// await _wallet?.send(EthereumAddress.fromHex(recipient), etherAmount);
final response =
await _wallet?.send(EthereumAddress.fromHex(recipient), etherAmount);
final receipt = await response?.wait();

log("Transaction receipt Hash: ${receipt?.userOpHash}");
} else {
log("No wallet available to send transaction");
}
}

Future<UserOperationResponse?> transferToken(
EthereumAddress recipient, w3d.EtherAmount amount) async {
final erc20 =
EthereumAddress.fromHex("0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9");

return await _wallet?.sendTransaction(
erc20, Contract.encodeERC20TransferCall(erc20, recipient, amount));
}
}
44 changes: 42 additions & 2 deletions example/lib/screens/create_account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,48 @@ class _CreateAccountScreenState extends State<CreateAccountScreen> {
}
},
icon: const Icon(Icons.key),
label: const Text('Create Safe Smart Account')),
)
label: const Text(
'Create Alchemy Light Account with private key')),
),
18.verticalSpace,
Container(
margin: const EdgeInsets.only(left: 135),
child: Text('OR', style: TextStyle(fontSize: 18.sp))),
24.verticalSpace,
Container(
margin: const EdgeInsets.only(left: 30),
child: TextButton.icon(
onPressed: () {
try {
context.read<WalletProvider>().createEOAWallet();
Navigator.pushNamed(context, '/home');
} catch (e) {
'Something went wrong: $e';
}
},
icon: const Icon(Icons.key),
label: const Text(
'Create Alchemy Light Account with seed phrase')),
),
18.verticalSpace,
Container(
margin: const EdgeInsets.only(left: 135),
child: Text('OR', style: TextStyle(fontSize: 18.sp))),
24.verticalSpace,
Container(
margin: const EdgeInsets.only(left: 30),
child: TextButton.icon(
onPressed: () {
try {
context.read<WalletProvider>().createSafeWallet();
Navigator.pushNamed(context, '/home');
} catch (e) {
'Something went wrong: $e';
}
},
icon: const Icon(Icons.key),
label: const Text('Create default Safe Account')),
),
],
),
);
Expand Down
5 changes: 0 additions & 5 deletions example/lib/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:variancedemo/providers/wallet_provider.dart';
Expand Down Expand Up @@ -167,9 +165,6 @@ class NFT extends StatelessWidget {

@override
Widget build(BuildContext context) {
final wallet = context.select(
(WalletProvider provider) => provider.wallet,
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expand Down
Loading
Loading