Skip to main content

Account Abstraction with CoW Shed SDK

The CoW Shed SDK enables account abstraction functionality for CoW Protocol, allowing smart contract wallets to pre-authorize and execute multiple calls on behalf of users. This acts as a user-owned smart contract (1/1 multisig) that can execute batched operations.

Overview

CoW Shed provides:
  • Pre-authorized calls: Sign calls off-chain and execute them later
  • Batch execution: Execute multiple calls in a single transaction
  • Gasless execution: Anyone can execute pre-authorized calls
  • Smart wallet support: Works with any EIP-1271 compatible wallet

Installation

npm install @cowprotocol/cow-shed
Or use it directly from the main SDK:
npm install @cowprotocol/cow-sdk

Basic Usage

Initialize CowShedSdk

import { CowShedSdk, ICoWShedCall, SupportedChainId } from '@cowprotocol/cow-shed'
import { EthersV6Adapter } from '@cowprotocol/sdk-ethers-v6-adapter'
import { JsonRpcProvider, Wallet } from 'ethers'

// Proper adapter initialization
const provider = new JsonRpcProvider('YOUR_RPC_URL')
const wallet = new Wallet('YOUR_PRIVATE_KEY', provider)
const adapter = new EthersV6Adapter({ provider, signer: wallet })

const cowShedSdk = new CowShedSdk(adapter)

Get CoW Shed Account Address

// Get the cow-shed account for any given chainId and owner's address
const cowShedAccount = cowShedSdk.getCowShedAccount(
  SupportedChainId.MAINNET,
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
)

console.log('CoW Shed Account:', cowShedAccount)

Sign and Execute Calls

// Prepare the calls you want to execute using the cow-shed account
const calls: ICoWShedCall[] = [
  {
    target: '<contract-address-1>',
    callData: 'call-data-1',
    value: 0n,
    isDelegateCall: true,
    allowFailure: false,
  },
  {
    target: '<contract-address-2>',
    callData: '<call-data-2>',
    value: 0n,
    isDelegateCall: true,
    allowFailure: false,
  },
]

// Sign the calls
const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  signer: wallet, // Optional - will use global adapter's signer if not provided
})

// Get the details of the pre-authorized call you need to send
const { signedMulticall, gasLimit } = preAuthorizedCall
const { to, data, value } = signedMulticall

// Execute the transaction using any wallet. The calldata has been pre-authed,
// so you don't need any special permissions to send this transaction
const anotherWallet = new Wallet('<another-private-key>', provider)
const tx = await anotherWallet.sendTransaction({
  to,
  data,
  value,
  gasLimit,
})

const receipt = await tx.wait()
console.log('Transaction executed:', receipt.hash)

Call Structure

ICoWShedCall Interface

interface ICoWShedCall {
  target: string           // Contract address to call
  callData: string         // Encoded function call data
  value: bigint           // ETH value to send (in wei)
  isDelegateCall: boolean // Whether to use delegatecall
  allowFailure: boolean   // Whether to allow this call to fail
}

Creating Calls

import { encodeFunctionData } from 'viem'

// Example: Approve ERC20 token
const approveCall: ICoWShedCall = {
  target: '0xTokenAddress',
  callData: encodeFunctionData({
    abi: ERC20_ABI,
    functionName: 'approve',
    args: [spenderAddress, amount]
  }),
  value: 0n,
  isDelegateCall: false,
  allowFailure: false,
}

// Example: Execute swap
const swapCall: ICoWShedCall = {
  target: '0xSwapContract',
  callData: encodeFunctionData({
    abi: SWAP_ABI,
    functionName: 'swap',
    args: [tokenIn, tokenOut, amountIn]
  }),
  value: 0n,
  isDelegateCall: false,
  allowFailure: false,
}

Advanced Features

Custom Nonce

Specify a custom nonce for replay protection:
const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  nonce: '0x1234567890abcdef', // Custom 32-byte nonce
})

Custom Deadline

Set an expiration time for the pre-authorized calls:
const futureTimestamp = BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour from now

const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  deadline: futureTimestamp,
})

Gas Limit Configuration

// Provide explicit gas limit
const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  gasLimit: 500000n,
})

// Or provide default gas limit if estimation fails
const preAuthorizedCall2 = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  defaultGasLimit: 500000n, // Used if estimation fails
})

Signing Schemes

import { SigningScheme } from '@cowprotocol/sdk-contracts-ts'

const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  signingScheme: SigningScheme.EIP712, // Default
  // Or: SigningScheme.ETHSIGN
})

Complete Example

Here’s a complete example of approving tokens and placing a CoW order:
import {
  CowShedSdk,
  ICoWShedCall,
  SupportedChainId
} from '@cowprotocol/cow-shed'
import {
  EthersV6Adapter
} from '@cowprotocol/sdk-ethers-v6-adapter'
import { encodeFunctionData, parseEther, parseUnits } from 'viem'
import { JsonRpcProvider, Wallet } from 'ethers'

// 1. Initialize
const provider = new JsonRpcProvider('https://mainnet.infura.io/v3/YOUR-KEY')
const wallet = new Wallet('YOUR_PRIVATE_KEY', provider)
const adapter = new EthersV6Adapter({ provider, signer: wallet })
const cowShedSdk = new CowShedSdk(adapter)

// 2. Get CoW Shed account
const ownerAddress = await wallet.getAddress()
const cowShedAccount = cowShedSdk.getCowShedAccount(
  SupportedChainId.MAINNET,
  ownerAddress
)

// 3. Prepare calls
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
const SETTLEMENT = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'

const calls: ICoWShedCall[] = [
  // Approve WETH
  {
    target: WETH,
    callData: encodeFunctionData({
      abi: [{
        name: 'approve',
        type: 'function',
        inputs: [
          { name: 'spender', type: 'address' },
          { name: 'amount', type: 'uint256' }
        ],
        outputs: [{ type: 'bool' }]
      }],
      functionName: 'approve',
      args: [SETTLEMENT, parseEther('1')]
    }),
    value: 0n,
    isDelegateCall: false,
    allowFailure: false,
  },
  // Set pre-signature for order
  {
    target: SETTLEMENT,
    callData: encodeFunctionData({
      abi: [{
        name: 'setPreSignature',
        type: 'function',
        inputs: [
          { name: 'orderUid', type: 'bytes' },
          { name: 'signed', type: 'bool' }
        ],
        outputs: []
      }],
      functionName: 'setPreSignature',
      args: [orderUid, true]
    }),
    value: 0n,
    isDelegateCall: false,
    allowFailure: false,
  },
]

// 4. Sign calls
const preAuthorizedCall = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
})

// 5. Execute (can be done by anyone)
const { signedMulticall, gasLimit } = preAuthorizedCall
const { to, data, value } = signedMulticall

const tx = await wallet.sendTransaction({
  to,
  data,
  value,
  gasLimit,
})

const receipt = await tx.wait()
console.log('Pre-authorized calls executed:', receipt.hash)

Use Cases

1. Gasless Order Placement

Users can sign orders off-chain and have relayers execute them:
const calls = [approveToken, setPreSignature]
const preAuthorized = await cowShedSdk.signCalls({ chainId, calls })

// Send preAuthorized to relayer service
await relayerService.execute(preAuthorized)

2. Batch Operations

Execute multiple operations atomically:
const calls = [
  approveToken1,
  approveToken2,
  createOrder,
  stakeTokens,
]

const preAuthorized = await cowShedSdk.signCalls({ chainId, calls })

3. Smart Wallet Integration

Use with Safe or other smart wallets:
// CoW Shed account is deterministic per owner
const safeAddress = '0xSafeWalletAddress'
const cowShedAccount = cowShedSdk.getCowShedAccount(
  SupportedChainId.MAINNET,
  safeAddress
)

// Sign with Safe
const preAuthorized = await cowShedSdk.signCalls({
  chainId: SupportedChainId.MAINNET,
  calls,
  signer: safeWallet,
})

Custom Factory Options

Customize the CoW Shed factory configuration:
import { ICoWShedOptions } from '@cowprotocol/cow-shed'

const customOptions: ICoWShedOptions = {
  factoryAddress: '0xCustomFactoryAddress',
  implementationAddress: '0xCustomImplementationAddress',
  proxyCreationCode: '0x...',
}

const cowShedSdk = new CowShedSdk(adapter, customOptions)

Error Handling

try {
  const preAuthorized = await cowShedSdk.signCalls({
    chainId: SupportedChainId.MAINNET,
    calls,
  })

  const tx = await wallet.sendTransaction(preAuthorized.signedMulticall)
  await tx.wait()
} catch (error) {
  if (error.message.includes('gas')) {
    console.error('Gas estimation failed - provide defaultGasLimit')
  } else if (error.message.includes('nonce')) {
    console.error('Nonce already used - generate new nonce')
  } else if (error.message.includes('deadline')) {
    console.error('Pre-authorization expired')
  } else {
    console.error('Execution failed:', error)
  }
}

Best Practices

Use allowFailure strategically

Set allowFailure: true for non-critical operations:
const calls: ICoWShedCall[] = [
  { ...criticalCall, allowFailure: false },
  { ...optionalCall, allowFailure: true },
]

Set appropriate deadlines

Don’t use MAX_UINT256 for production - set reasonable expiration:
const oneHour = BigInt(Math.floor(Date.now() / 1000) + 3600)
const preAuthorized = await cowShedSdk.signCalls({
  chainId,
  calls,
  deadline: oneHour,
})

Estimate gas carefully

Provide default gas limit for complex operations:
const preAuthorized = await cowShedSdk.signCalls({
  chainId,
  calls,
  defaultGasLimit: 1000000n, // Fallback if estimation fails
})

Use unique nonces

Generate unique nonces to prevent replay attacks:
const nonce = adapter.utils.formatBytes32String(
  `${Date.now()}-${Math.random()}`
)

Next Steps

  • Hooks - Execute custom logic before and after orders
  • Partner Fee - Configure partner fee collection
Last modified on March 4, 2026