Skip to main content

Universal Account Core SDK

The @layerg-ua-sdk/aa-sdk package is the core component of the Layer8 Universal Account SDK ecosystem. It provides essential functionality for working with Universal Accounts in web3 applications, enabling developers to leverage Account Abstraction without dealing with the underlying complexity.

Package Overview

This package serves as the primary interface for:

  • Connecting to blockchain networks and the Layer8 bundler service
  • Managing authentication and signatures
  • Creating and interacting with Universal Accounts
  • Building and sending smart contract transactions
  • Handling transaction receipts and events

Key Components

The aa-sdk package consists of several key components:

Providers

Provider classes that connect your application to the blockchain and Layer8 services:

  • AuthProvider: Manages authentication between your application and UA services
  • LayerProvider: The main interface for interacting with Universal Accounts
  • GAccountProvider: Extended functionality for G-type accounts

Account APIs

APIs for managing Universal Accounts:

  • BaseAccountAPI: Core account functionality
  • GAccountAPI: Advanced account features for G-type accounts
  • ERCAccountAPI: Support for ERC-4337 compatible accounts
  • SimpleAccountAPI: Simplified API for basic operations

Contract Interactions

Methods for interacting with smart contracts:

  • Building contract call requests
  • Executing contract transactions
  • Batch transactions support
  • Event handling utilities

User Operations

Tools for building and managing ERC-4337 User Operations:

  • Creating and customizing User Operations
  • Sending operations to bundlers
  • Gas estimation and optimization
  • Monitoring operation status

Installation

Install the package using npm or yarn:

# Using npm
npm install @layerg-ua-sdk/aa-sdk

# Using yarn
yarn add @layerg-ua-sdk/aa-sdk

Usage Approaches

The SDK supports two main approaches for working with Universal Accounts:

  1. System-managed AA wallet - Using the Layer8 services to handle most of the complexity
  2. Self-managed AA wallet - Direct control over User Operations for more advanced use cases

Both approaches are detailed below.

System-Managed Approach

Here's a simple example showing how to use the SDK with an existing authentication token (JWT):

import { 
buildContractCallRequest
} from '@layerg-ua-sdk/aa-sdk';

// If you already have a JWT from authenticating with the UA API,
// you can skip the AuthProvider initialization and directly build transaction requests

// Build a contract call request
const txRequest = buildContractCallRequest({
sender: "0x5da884e2602089AAc923E897b298282a40287C89", // Your UA wallet address
contractAddress: '0xContractAddress',
abi: CONTRACT_ABI,
method: 'transfer',
params: ['0xRecipient', '1000000000000000000']
});

// Send the transaction with your existing JWT
const response = await fetch(`${BUNDLER_ENDPOINT}/onchain/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`, // Your JWT
'x-signature': requestHeaders.signature,
'x-timestamp': `${requestHeaders.timestamp}`,
'x-api-key': requestHeaders.publicKey,
'origin': window.location.origin,
},
body: JSON.stringify({
"chainId": 2484,
"sponsor": true,
"transactionReq": {
"to": txRequest.to,
"value": "0",
"data": txRequest.data,
"maxPriorityFeePerGas": "1800000000"
}
}),
});

const txData = await response.json();
console.log("User operation hash:", txData.data.userOpHash);

// Wait for the transaction to be mined
const receipt = await waitForUserOperationReceipt(txData.data.userOpHash, 2484);
console.log(`Transaction confirmed in block ${receipt.receipt.blockNumber}`);

For cases where you need to initialize the providers and handle authentication within your application, you can use the following approach:

import { 
AuthProvider,
LayerProvider
} from '@layerg-ua-sdk/aa-sdk';

// Initialize authentication provider
const authProvider = new AuthProvider();
await authProvider.initialize({
bundlerUrl: 'https://bundler.layer8.network',
apiKey: 'your-api-key'
});

// Initialize layer provider
const layerProvider = new LayerProvider();
await layerProvider.initialize({
authProvider,
chainId: 2484 // U2U Chain ID
});

// Get account info
const accountInfo = await layerProvider.getAccount();
console.log(`Account address: ${accountInfo.address}`);

// ... continue with transaction building and sending

Self-Managed Approach

For more advanced use cases where you need direct control over User Operations, you can use the following approach:

Understanding User Operations

User Operations are the transaction format used in Account Abstraction systems. Unlike traditional Ethereum transactions, User Operations have a more complex structure that includes:

  • Sender account address
  • Nonce for replay protection
  • Call data for transaction execution
  • Gas parameters
  • Signature for validation

Creating User Operations

The LayerGOperation class is the primary tool for building User Operations:

import { LayerGOperation, GAccountAPI } from '@layerg-ua-sdk/aa-sdk';
import { ethers } from 'ethers';

// Setup provider
const provider = new ethers.providers.JsonRpcProvider('https://rpc-endpoint.network');
const signer = new ethers.Wallet(privateKey, provider);

// Create GAccountAPI instance
const accountParams = {
factoryAddress: '0xe18e5Bb12609acdDACe55BB978a7d9CF7315B6Fd',
provider,
walletId: 'your-wallet-id',
entryPointAddress: '0x803Cf2b820bcE4774DdfeB5CA13169Ef96fAc0d7',
owner: signer,
projectApiKey: 'your-api-key',
accountAddress: '0x4D6f435009A6fC36842e698915F2c9Df7f5f648e',
};

// Optionally add paymaster for gas sponsoring
if (useGasSponsoring) {
accountParams.paymasterAPI = new PaymasterAPI({
paymasterAddress: '0x67655470ec93f609A9232021D518e6225C3bC4fA',
provider,
});
}

const gAccount = new GAccountAPI(accountParams);

// Create LayerGOperation instance
const layerGOperationParams = {
smartAccountAPI: gAccount,
provider,
};

const layerGOperation = new LayerGOperation(layerGOperationParams);

// Build UserOperation from transaction request
const transactionReq = {
to: '0xRecipientAddress',
value: '1000000000000000', // 0.001 ETH in wei
data: '0x', // Empty data for simple transfer
maxPriorityFeePerGas: '18000000000', // 18 gwei
};

// Generate the UserOperation
const userOperation = await layerGOperation.buildUserOperation(transactionReq);

Sending User Operations to the Bundler

After building a User Operation, you can send it to the bundler service for execution:

// Build the API payload
const payload = {
chainId: 2484, // Chain ID (e.g., U2U Testnet)
sponsor: true, // Whether to use gas sponsoring
transactionReq: {
to: '0xRecipientAddress',
value: '1000000000000000', // 0.001 ETH
data: '0x',
maxPriorityFeePerGas: '18000000000', // 18 gwei
},
userOperation: userOperation, // The generated UserOperation
appApiKey: 'your-app-api-key',
};

// Generate required authentication headers
const headers = await createSignature('yourdomain.com');

// Send the request to the bundler API
const response = await fetch(`${BUNDLER_ENDPOINT}/onchain/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'x-signature': headers.signature,
'x-timestamp': `${headers.timestamp}`,
'x-api-key': headers.publicKey,
'origin': window.location.origin,
},
body: JSON.stringify(payload),
});

const result = await response.json();

// If successful, the response will include a userOpHash
if (result.success) {
console.log(`UserOperation Hash: ${result.data.userOpHash}`);

// Wait for the UserOperation receipt
const receipt = await waitForUserOperationReceipt(
result.data.userOpHash,
payload.chainId
);

console.log(`Transaction mined in block ${receipt.receipt.blockNumber}`);
}

Helper Functions for Contract Calls

For common smart contract interactions, you can use the simplified helper function buildContractCallRequest:

import { buildContractCallRequest } from '@layerg-ua-sdk/aa-sdk';

// Build a contract call request
const txRequest = buildContractCallRequest({
sender: '0xYourAccountAddress',
contractAddress: '0xContractAddress',
abi: CONTRACT_ABI,
method: 'transfer',
params: ['0xRecipient', '1000000000000000000'] // 1 token with 18 decimals
});

// Generated txRequest can be sent to the bundler API
// or used as input to buildUserOperation

This approach is more convenient for contract interactions, as it handles ABI encoding automatically.

Gas Estimation and Optimization

The SDK includes utilities for gas estimation:

// Get gas parameters for the current network
const gasParams = await layerGOperation.getGasPrice();
console.log(`Suggested max fee per gas: ${gasParams.maxFeePerGas}`);
console.log(`Suggested max priority fee: ${gasParams.maxPriorityFeePerGas}`);

// Estimate gas for a specific transaction
const gasEstimate = await layerGOperation.estimateGas(transactionReq);
console.log(`Estimated gas: ${gasEstimate}`);

Advanced Use Cases

Custom Nonce Management

For cases where you need to control the nonce manually:

// Get the current nonce
const nonce = await gAccount.getNonce();

// Build UserOperation with specific nonce
const userOperation = await layerGOperation.buildUserOperation(
transactionReq,
{ nonce: ethers.utils.hexValue(nonce) }
);

Custom Signature

If you need to handle signing separately:

// Build unsigned UserOperation
const unsignedUserOp = await layerGOperation.buildUserOperationWithoutSignature(
transactionReq
);

// Sign it manually
const signature = await customSigningFunction(unsignedUserOp);

// Apply the signature
unsignedUserOp.signature = signature;

Additional Resources

For more detailed information on using this package, refer to the specific component documentation:

The aa-sdk package works seamlessly with other Layer8 Universal Account SDK packages: