Smart Contract Wallet - Zerodev
Polynomial core contracts supports Account Abstracted (AA) Wallets based on ERC 4337. Currently we uses Zerodev as our primary AA provider and this docs will be containing configurations and examples of using zerodev Smart contract wallets for trade in Polynomial perps.
It is not mandatory to use AA wallets to trade on polynomial. But txs from AA wallet will be sponsored. You can save gas fees by using AA wallets rather than directly interacting with your EOA.
Bundler and Paymaster config
bundlerAPI: 'https://polynomial-mainnet.g.alchemy.com/v2/nUkjX-R-WfZwctQu_TLm0xQ7KI8TVXaD',
paymasterAPI: 'https://polynomial-mainnet.g.alchemy.com/v2/nUkjX-R-WfZwctQu_TLm0xQ7KI8TVXaD',
policy: '14119bab-6c62-4fed-8fe7-27ad5fcc5754'
Sample Code Snippets
AA client Setup (Typescript)
Uses Wagmi , Viem and zerodev sdk
import { getWalletClient } from '@wagmi/core';
import { signerToEcdsaValidator, getKernelAddressFromECDSA } from '@zerodev/ecdsa-validator';
import {createPublicClient} from 'viem';
import {
addressToEmptyAccount,
createKernelAccount,
createKernelAccountClient
} from '@zerodev/sdk';
const client = await getWalletClient(wagmiConfig);
const publicClient = createPublicClient({
transport: http(getZeroDevBundlerRPC(chain.id, true, ZERODEV_PROVIDER)),
chain: chain
});
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer: client,
entryPoint,
kernelVersion: KERNEL_V3_1
});
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: ecdsaValidator
},
entryPoint,
index: 8008n,
kernelVersion: "0.3.1"
});
const aaClient = createKernelAccountClient({
account,
chain,
pollingInterval: 500,
bundlerTransport: http(getZeroDevBundlerRPC(chain.id, false, ZERODEV_PROVIDER)),
paymaster: {
getPaymasterData(userOperation) {
return sponsorUserOperationPolynomial({
userOperation,
entryPoint,
chain
});
}
},
userOperation: {
estimateFeesPerGas() {
return {
maxFeePerGas: 0n,
maxPriorityFeePerGas: 0n
};
}
}
});
SponsorUserOperation function
const sponsorUserOperationPolynomial = async (params: {
userOperation: any;
entryPoint: entryPointType;
chain: Chain;
}) => {
const { userOperation, entryPoint, chain } = params;
if (chain.id !== polynomialSepolia.id && chain.id != polynomialChain.id) {
throw new Error('Invalid chain id');
}
const policyId = config[chain.id].erc4337.policy;
const bundlerAPI = config[chain.id].erc4337.bundlerAPI;
const options = {
method: 'POST',
headers: {
accept: 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify({
id: 1,
jsonrpc: '2.0',
method: 'alchemy_requestGasAndPaymasterAndData',
params: [
{
policyId: policyId,
entryPoint: entryPoint.address,
dummySignature: userOperation.signature,
userOperation: {
sender: userOperation.sender,
nonce: `0x${userOperation.nonce.toString(16)}`,
factory: userOperation.factory,
factoryData: userOperation.factoryData,
callData: userOperation.callData
}
}
]
})
};
const response = await (await fetch(bundlerAPI, options)).json();
const data = response;
const result: any = {
...userOperation,
paymaster: data.result.paymaster,
paymasterData: data.result.paymasterData,
preVerificationGas: BigInt(data.result.preVerificationGas),
verificationGasLimit: BigInt(data.result.verificationGasLimit),
callGasLimit: BigInt(data.result.callGasLimit),
paymasterVerificationGasLimit: BigInt(data.result.paymasterVerificationGasLimit),
paymasterPostOpGasLimit: BigInt(data.result.paymasterPostOpGasLimit),
maxFeePerGas: BigInt(data.result.maxFeePerGas),
maxPriorityFeePerGas: BigInt(data.result.maxPriorityFeePerGas)
};
return result;
};
Transaction Execution through Zerodev
export const executeTransactionThroughZerodev = async (
_smartContractWalletAddress: address,
_target: address[],
_callData: Hex[],
value: bigint[],
callType: string = CALL_TYPE.DELEGATE_CALL,
retry = false
) => {
const kernelClient = get(aaWalletClient); // Checkout setup code
const encodedCallData = await kernelClient.account.encodeCalls(
_target.map((target, index) => ({
to: target,
data: _callData[index],
value: value[index],
})),
callType
);
const entryPoint = kernelClient.account.entryPoint;
const userOp = await kernelClient.signUserOperation({
callData: encodedCallData,
preVerificationGas: ZERODEV_GAS_LIMITS.PRE_VERIFICATION_GAS,
callGasLimit: ZERODEV_GAS_LIMITS.CALL_GAS_LIMIT,
verificationGasLimit: ZERODEV_GAS_LIMITS.VERIFICATION_GAS_LIMIT,
maxFeePerGas: retry ? 1n : 0n,
maxPriorityFeePerGas: retry ? 1n : 0n
});
const userOpHash = await kernelClient.sendUserOperation({
entryPoint,
...userOp
});
return {
executedType: EXECUTED_TYPE.ZERO_DEV,
txData: userOpHash
};
};
Last updated
Was this helpful?