Based on this simulation, it seems like the transaction was reverted because of gas limit.
This is the piece of code that was trying to execute:
const service = new SafeApiKit({ chainId: BigInt(chainId) });
const safeSdk = await Safe.create({ ethAdapter, safeAddress });
const safeTransaction: SafeMultisigTransactionResponse =
await service.getTransaction(safeTxHash);
const isValidTx = await safeSdk
.isValidTransaction(safeTransaction)
.catch(() => false);
if (isValidTx) {
const txResponse = await safeSdk.executeTransaction(safeTransaction);
await txResponse.transactionResponse?.wait();
return { hash: txResponse.hash, error: null };
}
return {
hash: null,
error: new Error('Invalid transaction. Please check your Safe wallet'),
};
Maybe the transaction would have been valid if I could set a gasLimit on the safe transaction?
Further context:
Safe transaction hash: 0x84e066388c54661c56a5429d3b78d1f1a1820762ff1c3c5a020587178c4c0c00
https://app.safe.global/transactions/history?safe=eth:0x2a69F406384DCEee59480010037d4499136Dd1bf https://etherscan.io/tx/0x170eb706ce48570b036509007f66a8781bc84860d1f89e04be71f2ebc1ac86d2#internal
Notice the Safe Multisend 1.1.1 fail.
Tried to mimic the 1.1.1 Safe transactions in a newly created safe and same transactions type that failed above successfully passed: https://app.safe.global/transactions/history?safe=eth:0xC8B8eCD2255ff75707e69E1dB08DD01C4024b06B
Trying to understand why the transactions could have failed in the eth:0x2a69F406384DCEee59480010037d4499136Dd1bf
Safe
Here's side by side of 2 transactions
- https://etherscan.io/tx/0x170eb706ce48570b036509007f66a8781bc84860d1f89e04be71f2ebc1ac86d2/advanced#internal (Failed)
- https://etherscan.io/tx/0xb0768ed8e15b3e0eb5287ce4906df901a5fac90471cca8dc2a3db791f6492251/advanced#internal (Succeeded)
Looking at the tests for isValidTransaction
it doesn't look like there is anything about order: https://github.com/safe-global/safe-core-sdk/blob/d6e2fe955c75fad3a812d5537afbfe082aa840af/packages/protocol-kit/tests/e2e/execution.test.ts#L34
Code to create batch transaction
/ Processes a batch of transactions
const batchTransaction = async (
bills: Bill[],
chainId: number,
safeAddress: string
): Promise<TransactionResult> => {
try {
const ethAdapter = await createEthAdapter();
if (!ethAdapter || !signer) return { hash: null, error: null };
const service = new SafeApiKit({ chainId: BigInt(chainId) });
const safeSdk = await Safe.create({ ethAdapter, safeAddress });
const nextNonce = await service.getNextNonce(safeAddress);
const transactions = await Promise.all(
bills.map(async bill => {
// Get token price data for the bill
const [data] = await getTokenPriceAPI(bill.tokenId?.toString() || '');
if (!data)
return {
to: getAddress(bill.paidToAddress),
value: '0',
data: '0x',
operation: OperationType.Call,
};
// Calculate transaction amount based on token data
const amountToParse = (Number(bill.amount) / data.price).toFixed(6);
const amount = bill.networkTokenId
? parseUnits(amountToParse, data.decimals)
: parseEther(amountToParse);
// Create ERC20 transfer transaction
if (bill.networkTokenId) {
// Construct ERC20 transfer or default transaction
const transactionData = bill.networkTokenId
? new Contract(
bill.networkTokenId,
erc20ABI,
signer
).interface.encodeFunctionData('transfer', [
bill.paidToAddress,
amount,
])
: '0x';
return {
to: getAddress(bill.networkTokenId),
value: '0',
data: transactionData,
operation: OperationType.Call,
};
}
// Default transaction
return {
to: getAddress(bill.paidToAddress),
value: amount.toString(),
data: '0x',
operation: OperationType.Call,
};
})
);
const safeTransaction = await safeSdk.createTransaction({
transactions,
options: { nonce: nextNonce },
});
const safeTxHash = await safeSdk.getTransactionHash(safeTransaction);
const signature = await safeSdk.signTransactionHash(safeTxHash);
// Propose the batch transaction
await service.proposeTransaction({
safeAddress: await safeSdk.getAddress(),
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: await signer.getAddress(),
senderSignature: signature.data,
});
return { hash: safeTxHash, error: null };
} catch (err) {
return {
hash: null,
error: err instanceof Error ? err : new Error('Unknown error'),
};
}
};
Code to execute transaction
const executeTransaction = async (
safeTxHash: string,
safeAddress: string,
chainId: string
): Promise<TransactionResult> => {
try {
const ethAdapter = await createEthAdapter();
if (!ethAdapter || !signer)
return {
hash: null,
error: new Error('Ethereum adapter or signer is missing'),
};
const service = new SafeApiKit({ chainId: BigInt(chainId) });
const safeSdk = await Safe.create({ ethAdapter, safeAddress });
const safeTransaction: SafeMultisigTransactionResponse =
await service.getTransaction(safeTxHash);
const isValidTx = await safeSdk
.isValidTransaction(safeTransaction)
.catch(() => false);
if (isValidTx) {
const txResponse = await safeSdk.executeTransaction(safeTransaction);
await txResponse.transactionResponse?.wait();
return { hash: txResponse.hash, error: null };
}
return {
hash: null,
error: new Error('Invalid transaction. Please check your Safe wallet'),
};
} catch (err) {
return {
hash: null,
error: err instanceof Error ? err : new Error('Unknown error'),
};
}
};