Skip to main content

AnteHandler

The AnteHandler decorators are provided for the app - Dchain to use the Abstract Account to decide if a transaction has the correct authentication data to be executed. Previously the default is to check that the signature data is indeed valid and from the correct key.

The Proxy smart accounts implements Sudo entry_points to allow the process from the AnteHandler to check if the valid credential is provided.

Overview

The abstractaccount module provides three main decorators that integrate with Dchain's transaction processing pipeline:

  1. SetAbstractAccountPubKeyDecorator: Sets NilPubKey for accounts implementing AbstractAccountI
  2. BeforeTxDecorator: Validates credentials by calling the account contract before transaction execution
  3. AfterTxDecorator: Placeholder for post-transaction hooks (currently not implemented)

These decorators work together to enable custom authentication logic for abstract accounts while maintaining compatibility with standard SDK accounts.

Decorators

1. SetAbstractAccountPubKeyDecorator

Location: x/abstractaccount/ante.go:51-109

This decorator runs after the standard SetPubKeyDecorator and handles the special case where accounts implement the AbstractAccountI interface.

Purpose: Set a NilPubKey for abstract accounts that don't have a public key set yet.

Flow:

  1. Extract signers from the transaction
  2. For each signer, retrieve the account from the account keeper
  3. Check if the account implements AbstractAccountI interface
  4. If yes, check if IsAbstractAccount() returns true
  5. If the account doesn't have a pubkey set, create and set a NilPubKey
  6. Save the updated account to the account keeper

Why NilPubKey?

Abstract accounts don't use traditional cryptographic signatures for authentication. Instead, they rely on smart contract logic. The NilPubKey is a special public key type that:

  • Returns the account address when Address() is called
  • Panics if VerifySignature() is called (should never happen for abstract accounts)
  • Prevents nil pointer panics during transaction signing data preparation

Code Reference:

func (sapkd SetAbstractAccountPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Loop through each signer
for _, signer := range signers {
acc, err := authante.GetSignerAcc(ctx, sapkd.ak, signer)

// Check if account implements AbstractAccountI
absAccI, ok := acc.(types.AbstractAccountI)
if !ok {
continue
}

// Check if it's actually an abstract account
if !absAccI.IsAbstractAccount() {
continue
}

// Set NilPubKey if not already set
if acc.GetPubKey() == nil {
nilPubKey := vestingtypes.NewNilPubKey(acc.GetAddress())
err = acc.SetPubKey(nilPubKey)
sapkd.ak.SetAccount(ctx, acc)
}
}
return next(ctx, tx, simulate)
}

2. BeforeTxDecorator

Location: x/abstractaccount/ante.go:112-231

This is the main decorator that implements custom authentication for abstract accounts.

Purpose: Validate transaction credentials by invoking the account contract's BeforeTx sudo entry point.

Flow:

  1. Determine if this is an Abstract Account Transaction:

    • Transaction must have exactly one signer and one signature
    • The signer must implement AbstractAccountI and IsAbstractAccount() must return true
    • If not, delegate to the standard SigVerificationDecorator
  2. Store Signer Address: Save the abstract account address to module store for potential use in PostHandler

  3. Verify Sequence Number: Check that the signature sequence matches the account sequence to prevent replay attacks

  4. Prepare Contract Sudo Message:

    • Convert transaction messages to []Any format
    • Extract sign bytes and credentials from the signature data
    • Build the BeforeTx sudo message
  5. Invoke Account Contract: Call the account contract's sudo entry point with gas limit specified in module params

  6. Continue to Next Decorator: If contract execution succeeds, proceed with transaction execution

Credentials vs Signatures:

The decorator uses the term "credentials" instead of "signatures" because abstract accounts can use any form of authentication proof, not just cryptographic signatures. Examples include:

  • WebAuthn signatures (passkeys)
  • Zero-knowledge proofs
  • Multi-factor authentication tokens
  • Time-based one-time passwords (TOTP)

Sudo Message Format:

{
"before_tx": {
"msgs": [...], // Transaction messages as Any types
"tx_bytes": "...", // Sign bytes for the transaction
"cred_bytes": "...", // Credential bytes (e.g., signature)
"simulate": false // Whether this is a simulation
}
}

Code Reference:

func (d BeforeTxDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Check if this is an abstract account transaction
isAbstractAccountTx, signerAcc, sig, err := IsAbstractAccountTx(ctx, tx, d.ak)
if !isAbstractAccountTx {
// Delegate to standard verification
svd := authante.NewSigVerificationDecorator(d.ak, d.signModeHandler)
return svd.AnteHandle(ctx, tx, simulate, next)
}

// Save signer address for posthandler
err = d.aak.SetSignerAddress(ctx, signerAcc.GetAddress())

// Check sequence number
if sig.Sequence != signerAcc.GetSequence() {
return ctx, sdkerrors.ErrWrongSequence
}

// Prepare sudo message
sudoMsgBytes, err := json.Marshal(&types.AccountSudoMsg{
BeforeTx: &types.BeforeTx{
Msgs: msgAnys,
TxBytes: signBytes,
CredBytes: sigBytes,
Simulate: simulate,
},
})

// Invoke account contract with gas limit
if err := utils.SudoWithGasLimit(ctx, d.aak.ContractKeeper(), signerAcc.GetAddress(), sudoMsgBytes, params.MaxGasBefore); err != nil {
return ctx, err
}

return next(ctx, tx, simulate)
}

3. AfterTxDecorator

Location: x/abstractaccount/ante.go:233-251

This decorator is a PostDecorator that runs after transaction execution.

Purpose: Placeholder for future post-transaction hooks for abstract accounts.

Current Implementation: Currently not implemented - simply passes through to the next handler.

Potential Future Uses:

  • Post-transaction validation
  • Event emission for abstract account transactions
  • Plugin execution hooks
  • State update notifications

Code Reference:

func (d AfterTxDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate, success bool, next sdk.PostHandler) (sdk.Context, error) {
// Currently we only support BeforeTx
return next(ctx, tx, simulate, success)
}

Helper Functions

IsAbstractAccountTx

Location: x/abstractaccount/ante.go:255-295

Determines whether a transaction qualifies as an abstract account transaction.

Criteria:

  1. Transaction has exactly one signer
  2. Transaction has exactly one signature
  3. The signer account implements AbstractAccountI interface
  4. The account's IsAbstractAccount() method returns true

Returns:

  • bool: Whether this is an abstract account transaction
  • sdk.AccountI: The signer account (if applicable)
  • *txsigning.SignatureV2: The signature data (if applicable)
  • error: Any error encountered

SigVerificationGasConsumer

Location: x/abstractaccount/ante.go:35-47

Custom gas consumer for signature verification that handles NilPubKey specially.

Behavior:

  • For NilPubKey: Does not consume gas (gas is consumed during contract execution)
  • For other public keys: Delegates to the default SDK gas consumer

Code Reference:

func SigVerificationGasConsumer(meter storetypes.GasMeter, sig txsigning.SignatureV2, params authtypes.Params) error {
switch sig.PubKey.(type) {
case *vestingtypes.NilPubKey:
return nil // No gas consumed for NilPubKey
default:
return authante.DefaultSigVerificationGasConsumer(meter, sig, params)
}
}

Integration with App

The decorators are typically integrated into the ante handler chain in app/ante.go:

anteDecorators := []sdk.AnteDecorator{
// ... other decorators
authante.NewSetPubKeyDecorator(accountKeeper),
abstractaccount.NewSetAbstractAccountPubKeyDecorator(accountKeeper),
// ... other decorators
abstractaccount.NewBeforeTxDecorator(abstractAccountKeeper, accountKeeper, signModeHandler),
// ... other decorators
}

The AfterTxDecorator is integrated into the post handler chain:

postDecorators := []sdk.PostDecorator{
abstractaccount.NewAfterTxDecorator(abstractAccountKeeper),
}

Gas Limits

The module parameters include a MaxGasBefore setting that limits the gas consumption during the BeforeTx contract execution. This prevents abstract accounts from consuming excessive gas during authentication, which could be used for denial-of-service attacks.

Error Handling

If the contract execution in BeforeTxDecorator fails (e.g., invalid credentials, contract error), the ante handler returns an error and the transaction is rejected. Common failure scenarios:

  • Invalid credentials provided
  • Contract execution error
  • Gas limit exceeded
  • Sequence number mismatch
  • Account not found