Skip to main content

Account Management

Manage account storage, guardians (2FA), and account-level operations.

Overview

The AccountController provides operations for:

  • Storage - Save key-value pairs to account storage
  • Guardians - Set up 2FA protection with guardian accounts
  • Account Protection - Guard and unguard accounts

AccountController

import 'package:abidock_mvx/abidock_mvx.dart';

final controller = AccountController(
chainId: const ChainId.devnet(),
gasLimitEstimator: GasEstimator(networkProvider: provider),
);

Account Storage

Save arbitrary data to your account's on-chain storage:

import 'dart:convert';
import 'dart:typed_data';

final account = await Account.fromPem('wallet.pem');
final accountInfo = await provider.getAccount(account.address);

// Prepare key-value pairs
final input = SaveKeyValueInput(
keyValuePairs: {
Uint8List.fromList(utf8.encode('username')):
Uint8List.fromList(utf8.encode('alice')),
Uint8List.fromList(utf8.encode('preferences')):
Uint8List.fromList(utf8.encode('{"theme":"dark","lang":"en"}')),
},
);

final tx = await controller.createTransactionForSavingKeyValue(
account,
accountInfo.nonce,
input,
);

final hash = await provider.sendTransaction(tx);
print('Storage updated: $hash');

Storage Use Cases

KeyValue ExampleUse Case
usernamealiceDisplay name
avataripfs://Qm...Profile picture
preferences{"theme":"dark"}User settings
metadataJSON objectApplication data
Storage Costs

Each byte stored costs gas. Keep values concise for cost efficiency.

Guardian (2FA) Protection

Guardians provide two-factor authentication for accounts. When enabled, sensitive transactions require both the account signature and guardian co-signature.

Set Guardian

Register a guardian for your account:

final guardianInput = SetGuardianInput(
guardianAddress: Address.fromBech32('erd1guardian...'),
serviceId: 'twikey-2fa', // 2FA service provider
);

final tx = await controller.createTransactionForSettingGuardian(
account,
accountInfo.nonce,
guardianInput,
);

final hash = await provider.sendTransaction(tx);
print('Guardian set: $hash');

Guard Account

Activate guardian protection:

final tx = await controller.createTransactionForGuardingAccount(
account,
accountInfo.nonce,
);

final hash = await provider.sendTransaction(tx);
print('Account is now guarded');
Protection Active

After guarding, sensitive transactions will fail without guardian co-signature.

Unguard Account

Remove guardian protection (requires guardian co-signature):

final tx = await controller.createTransactionForUnguardingAccount(
account,
accountInfo.nonce,
options: BaseControllerInput(
guardian: guardianAddress, // Guardian must co-sign
),
);

// Transaction needs guardian signature from 2FA service
final hash = await provider.sendTransaction(tx);
print('Account unguarded');

Using Guardian with Controllers

When your account is guarded, pass the guardian in BaseControllerInput:

// Smart contract call with guardian
final scController = SmartContractController(
contractAddress: contractAddress,
networkProvider: provider,
abi: myAbi,
);

final tx = await scController.call(
account: account,
nonce: accountInfo.nonce,
endpointName: 'withdraw',
arguments: [BigInt.from(1000)],
options: BaseControllerInput(
gasLimit: GasLimit(10000000),
guardian: guardianAddress, // Include guardian for co-signing
),
);

// Transaction will be prepared for guardian co-signature

Token Transfer with Guardian

final transferController = TransfersController(
chainId: const ChainId.mainnet(),
);

final tx = await transferController.createTransactionForTokenTransfer(
account,
accountInfo.nonce,
TokenTransferInput(
receiver: recipient,
transfers: [
TokenTransfer.fungible(
tokenIdentifier: 'USDC-c76f1f',
amount: BigInt.from(1000000), // 1 USDC (6 decimals)
),
],
),
baseOptions: BaseControllerInput(
guardian: guardianAddress,
),
);

Complete Example

import 'package:abidock_mvx/abidock_mvx.dart';
import 'dart:convert';
import 'dart:typed_data';

void main() async {
final provider = GatewayNetworkProvider.devnet();
final account = await Account.fromMnemonic('your mnemonic...');

final controller = AccountController(
chainId: const ChainId.devnet(),
gasLimitEstimator: GasEstimator(networkProvider: provider),
);

var accountInfo = await provider.getAccount(account.address);

// 1. Save profile data
final storageInput = SaveKeyValueInput(
keyValuePairs: {
Uint8List.fromList(utf8.encode('profile')):
Uint8List.fromList(utf8.encode('{"name":"Alice","joined":"2024"}')),
},
);

var tx = await controller.createTransactionForSavingKeyValue(
account,
accountInfo.nonce,
storageInput,
);
await provider.sendTransaction(tx);
print('Profile saved');

// 2. Set up guardian (optional)
accountInfo = await provider.getAccount(account.address);

final guardianAddress = Address.fromBech32('erd1guardian...');
final guardianInput = SetGuardianInput(
guardianAddress: guardianAddress,
serviceId: 'my-2fa-service',
);

tx = await controller.createTransactionForSettingGuardian(
account,
accountInfo.nonce,
guardianInput,
);
await provider.sendTransaction(tx);
print('Guardian configured');

// 3. Activate guardian protection
accountInfo = await provider.getAccount(account.address);

tx = await controller.createTransactionForGuardingAccount(
account,
accountInfo.nonce,
);
await provider.sendTransaction(tx);
print('Account is now protected by 2FA');
}

Guardian Service Providers

Guardian services handle the 2FA authentication:

ServiceDescription
twikey-2faTwikey 2FA service
CustomYour own guardian service

Best Practices

  1. Backup Guardian - Ensure you can access guardian service before activating
  2. Test First - Test guardian flow on devnet before mainnet
  3. Storage Keys - Use consistent key naming conventions
  4. JSON Values - Store structured data as JSON for flexibility
  5. Guardian Security - Guardian address should be highly secure

Security Considerations

  • Guardian protection adds security but also complexity
  • Lost guardian access can lock your account
  • Consider using hardware wallets for guardian accounts
  • Test unguard flow before activating on mainnet