The problem was that Defender provides relayer addresses in non-checksummed form.
Safe API requires exactly this address format.
So, if you replace the code with this, then everything works perfectly:
import Web3 from 'web3';
import { ethers, utils } from 'ethers';
import SafeApiKit from '@safe-global/api-kit';
import EthersAdapter from '@safe-global/safe-ethers-lib';
import Safe from '@safe-global/safe-core-sdk';
import { SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types';
import { DefenderRelayProvider, DefenderRelaySigner } from 'defender-relay-client/lib/ethers';
const txServiceUrl = 'http://localhost:8000/txs/';
const safeAddress = '0x512560bB71b98a60bC7e87719D2B8573C935Ff58';
const tokenAddress = '0xFAA27935213A149c5f81442C30BaaD36b15863A9';
const abi = 'erc-20 token abi...';
const amount = '100'; // 100 erc-20 tokens to burn
const {
API_KEY,
API_SECRET,
} = process.env;
// Create defender signer:
const credentials = { apiKey: API_KEY!, apiSecret: API_SECRET! };
const relayerProvider = new DefenderRelayProvider(credentials);
const signerOrProvider = new DefenderRelaySigner(credentials, relayerProvider, {
speed: 'fast'
});
// Convert address to checksummed:
let senderAddress = await signerOrProvider.getAddress();
if (!Web3.utils.checkAddressChecksum(senderAddress)) {
senderAddress = Web3.utils.toChecksumAddress(senderAddress);
}
// Create safeService instance:
const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider
});
const safeService = new SafeApiKit({
txServiceUrl,
ethAdapter
});
// Create transactoin description:
const contractInterface = new utils.Interface(abi);
const data = contractInterface.encodeFunctionData('burn', [ethers.utils.parseEther(amount)]);
const safeTransactionData: SafeTransactionDataPartial = {
to: tokenAddress,
value: '0',
data,
operation: 0
};
// Create transaction:
const safeSdk = await Safe.create({
ethAdapter,
safeAddress
});
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData });
// Try to propose transaction:
const safeTxHash = await safeSdk.getTransactionHash(safeTransaction);
const senderSignature = await safeSdk.signTransactionHash(safeTxHash);
await safeService.proposeTransaction({
safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress,
senderSignature: senderSignature.data
});
console.log('DONE!!!!');