Stack Exchange
Problem with propose a custom transaction (for ERC-20 token)
I have a problem with creating a custom transaction proposal.
Here is my code:
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'
});

// 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: await signerOrProvider.getAddress(),
    senderSignature: senderSignature.data
});

console.log('DONE!!!!');

But when i try to run this code with ts-node i get an error:
Error: Unprocessable Entity
    at sendRequest (/home/msolovyev/crypto-projects/safe-test/node_modules/@safe-global/api-kit/src/utils/httpRequests.ts:58:9)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async proposeBurnFromMultisig (/home/msolovyev/crypto-projects/safe-test/src/multisigs.ts:96:5)

Please tell me how to correctly calculate the value of the data property for a transaction?

There is a few things that might cause your problem, an incorrect transaction data or an incorrect environment setup, first verify that safeAddress and tokenAddress are correct, then check that the ABI you are using is correct for the ERC-20 token you are interacting with, get more detailed error by replacing this
await safeService.proposeTransaction({
    safeAddress,
    safeTransactionData: safeTransaction.data,
    safeTxHash,
    senderAddress: await signerOrProvider.getAddress(),
    senderSignature: senderSignature.data
}); 

by this
try {
    await safeService.proposeTransaction({
        safeAddress,
        safeTransactionData: safeTransaction.data,
        safeTxHash,
        senderAddress: await signerOrProvider.getAddress(),
        senderSignature: senderSignature.data
    });
} catch (error) {
    console.error("Error while proposing transaction:", error.response ? error.response.data : error.message);
}

you can also check your local Gnosis Safe Transaction Service and that it is reachable at http://localhost:8000 check also that your API key and secret are valid.
edit: the error Error while proposing transaction: Unprocessable Entity mean  the API request you are making contains malformed data or is missing required fields, lets debug further, check API_KEY and API_SECRET are being loaded correctly
console.log('API Key:', API_KEY);
console.log('API Secret:', API_SECRET);

also
console.log('Safe Address:', safeAddress);
console.log('Token Address:', tokenAddress);

be sure that there is no incorrect or missing information, if that persist reach out to Gnosis Safe team for assistance

Thanks for the answer! However, it didn't work for me.  Tx service is available, safeAddress and tokenAddress is correct. ABI is correct. The error message is: Error while proposing transaction: Unprocessable Entity

I updated my answer

All parameters are filled, the request goes to the service, but come back with 422 error:
{
size: 0,
timeout: 0,
[Symbol(Response internals)]: {
url: 'http://localhost:8000/txs/api/v1/safes/0x512560bB71b98a60bC7e87719D2B8573C935Ff58/multisig-transactions/',
status: 422,
statusText: 'Unprocessable Entity',
headers: Headers { [Symbol(map)]: [Object: null prototype] },
counter: 0
}
}

there is still an issue with the data being sent in the API request

I found out that the problem is in the defender signer.
If you use the code,
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
    const signerOrProvider = new ethers.Wallet(DEVELOPER_WALLET_KEY!, provider);

then everything works. So, I have to go to OpenZeppelin)

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';
// ..
let senderAddress = await signerOrProvider.getAddress();
if (!Web3.utils.checkAddressChecksum(senderAddress)) {
    senderAddress = Web3.utils.toChecksumAddress(senderAddress);
}
// ...
await safeService.proposeTransaction({
// ...
    senderAddress
// ...
});

