Skip to content

Commit

Permalink
safe batch transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
code-z2 committed Apr 5, 2024
1 parent 686de74 commit bb76e89
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 37 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.1.0-r3

* Add mutisend address to constants
* Add support for Safe account batch transactions

## 0.1.0-r2

* Fix safe transaction encoding
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ The smart wallet factory handles the creation of smart wallet instances. Make su
final SmartWalletFactory smartWalletFactory = SmartWalletFactory(chain, signer);
```

#### To Create a Simple Account Smart Wallet
#### To Create a Simple Smart Account

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

#### To create a P256 Smart Account
#### To create a P256 Smart Account (Secure Enclave/Keystore)

```dart
final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, salt);
Expand All @@ -110,7 +110,7 @@ final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, s
print("p256 wallet address: ${wallet.address.hex}");
```

#### To create a Safe Smart Account
#### To create a [Safe](https://safe.global) Smart Account

```dart
final Smartwallet wallet = await smartWalletFactory
Expand Down
20 changes: 2 additions & 18 deletions example/lib/providers/wallet_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class WalletProvider extends ChangeNotifier {
: _chain = Chains.getChain(Network.baseTestnet)
..accountFactory = EthereumAddress.fromHex(
"0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070")
..bundlerUrl = "https://api.pimlico.io/v2/84532/rpc?apikey="
..bundlerUrl =
"https://api.pimlico.io/v2/84532/rpc?apikey=YOUR_API_KEY"
..paymasterUrl = "https://paymaster.optimism.io/v1/84532/rpc";

Future<void> registerWithPassKey(String name,
Expand Down Expand Up @@ -137,23 +138,6 @@ class WalletProvider extends ChangeNotifier {
ContractAbis.get("ERC721_SafeMint"), [_wallet?.address]));
await tx1?.wait();

// mints erc20 tokens
final tx2 = await _wallet?.sendTransaction(
erc20,
Contract.encodeFunctionCall(
"mint", erc20, ContractAbis.get("ERC20_Mint"), [
_wallet?.address,
w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 20).getInWei
]));
await tx2?.wait();

// transfers the tokens
final tx3 = await _wallet?.sendTransaction(
erc20,
Contract.encodeERC20TransferCall(
erc20, deployer, w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 18)));
await tx3?.wait();
log("trying batched transaction");
await sendBatchedTransaction();
}

Expand Down
2 changes: 2 additions & 0 deletions lib/src/4337/chains.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ class Constants {
EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a");
static final EthereumAddress safeModuleSetupAddress =
EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb");
static final EthereumAddress safeMultiSendaddress =
EthereumAddress.fromHex("0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526");

Constants._();
}
Expand Down
38 changes: 38 additions & 0 deletions lib/src/4337/safe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,42 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase {
op.paymasterAndData,
hexToBytes(getSafeSignature(op.signature, blockInfo))
]);

Uint8List getSafeMultisendCallData(List<EthereumAddress> recipients,
List<EtherAmount>? amounts, List<Uint8List>? innerCalls) {
Uint8List packedCallData = Uint8List(0);

for (int i = 0; i < recipients.length; i++) {
Uint8List operation = Uint8List.fromList([0]);
assert(operation.length == 1);
Uint8List to = recipients[i].addressBytes;
assert(to.length == 20);
Uint8List value = amounts != null
? padTo32Bytes(amounts[i].getInWei)
: padTo32Bytes(BigInt.zero);
assert(value.length == 32);
Uint8List dataLength = innerCalls != null
? padTo32Bytes(BigInt.from(innerCalls[i].length))
: padTo32Bytes(BigInt.zero);
assert(dataLength.length == 32);
Uint8List data =
innerCalls != null ? innerCalls[i] : Uint8List.fromList([]);
Uint8List encodedCall = Uint8List.fromList(
[...operation, ...to, ...value, ...dataLength, ...data]);
packedCallData = Uint8List.fromList([...packedCallData, ...encodedCall]);
}

return Contract.encodeFunctionCall(
"multiSend",
Constants.safeMultiSendaddress,
ContractAbis.get("multiSend"),
[packedCallData]);
}

Uint8List padTo32Bytes(BigInt number) {
final bytes = intToBytes(number);
return bytes.length < 32
? Uint8List.fromList([...Uint8List(32 - bytes.length), ...bytes])
: bytes;
}
}
23 changes: 18 additions & 5 deletions lib/src/4337/wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,28 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase {

@override
Future<UserOperationResponse> sendBatchedTransaction(
List<EthereumAddress> recipients, List<Uint8List> calls,
{List<EtherAmount>? amounts}) =>
sendUserOperation(buildUserOperation(
List<EthereumAddress> recipients, List<Uint8List> calls,
{List<EtherAmount>? amounts}) {
final isSafe = hasPlugin("safe");
if (isSafe) {
final innerCall = plugin<_SafePlugin>('safe')
.getSafeMultisendCallData(recipients, amounts, calls);
return sendUserOperation(buildUserOperation(
callData: Contract.executeBatch(
walletAddress: _walletAddress,
recipients: [Constants.safeMultiSendaddress],
amounts: [],
innerCalls: [innerCall],
isSafe: true)));
} else {
return sendUserOperation(buildUserOperation(
callData: Contract.executeBatch(
walletAddress: _walletAddress,
recipients: recipients,
amounts: amounts,
innerCalls: calls,
isSafe: hasPlugin("safe"))));
innerCalls: calls)));
}
}

@override
Future<UserOperationResponse> sendSignedUserOperation(UserOperation op) =>
Expand Down
4 changes: 4 additions & 0 deletions lib/src/abis/contract_abis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class ContractAbis {
abi =
'[{"type":"function","name":"setup","inputs":[{"name":"_owners","type":"address[]","internalType":"address[]"},{"name":"_threshold","type":"uint256","internalType":"uint256"},{"name":"to","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"fallbackHandler","type":"address","internalType":"address"},{"name":"paymentToken","type":"address","internalType":"address"},{"name":"payment","type":"uint256","internalType":"uint256"},{"name":"paymentReceiver","type":"address","internalType":"address payable"}],"outputs":[],"stateMutability":"nonpayable"}]';
break;
case 'multiSend':
abi =
'[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes","name":"transactions","type":"bytes"}],"name":"multiSend","outputs":[],"stateMutability":"payable","type":"function"}]';
break;
default:
throw 'ABI of $name is not available by default. Please provide the ABI manually.';
}
Expand Down
32 changes: 22 additions & 10 deletions lib/src/common/contract.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,12 @@ class Contract {
innerCallData ?? Uint8List.fromList([])
];

if (isSafe) params.add(BigInt.zero);
final method = isSafe ? 'executeUserOpWithErrorString' : 'execute';
String method = 'execute';

if (isSafe) {
method = 'executeUserOpWithErrorString';
params.add(BigInt.zero);
}

return encodeFunctionCall(
method,
Expand Down Expand Up @@ -331,24 +335,32 @@ class Contract {
List<EtherAmount>? amounts,
List<Uint8List>? innerCalls,
bool isSafe = false}) {
if (isSafe) {
throw UnimplementedError(
"Batch Transactions with Safe are not yet implemented.");
}

final params = [
List params = [
recipients,
amounts?.map<BigInt>((e) => e.getInWei) ?? [],
innerCalls ?? Uint8List.fromList([]),
];

if (innerCalls == null || innerCalls.isEmpty) {
require(amounts != null && amounts.isNotEmpty, "malformed batch request");
}

String method = 'executeBatch';

if (isSafe) {
method = 'executeUserOpWithErrorString';
params = [
recipients[0], // multisend contract
EtherAmount.zero().getInWei, // 0 value to be passed
innerCalls?[0], // the encoded multisend calldata
BigInt.one // specifying delegate call operation
];
}

return encodeFunctionCall(
'executeBatch',
method,
walletAddress,
ContractAbis.get('executeBatch'),
ContractAbis.get(method),
params,
);
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: variance_dart
description: An Account Abstraction (4337) Development kit, for quickly building mobile web3 apps and smart wallets.
version: 0.1.0-r2
version: 0.1.0-r3
documentation: https://docs.variance.space
homepage: https://variance.space
repository: https://github.com/vaariance/variance-dart
Expand Down

0 comments on commit bb76e89

Please sign in to comment.