I'm currently facing an issue on one of the tools I'm working on. The goal of the tool is to have a CI job that sync a Safe account to a github repo. When creating new proposals and they get merged to master, the proposals are then crafted and sent to the safe transaction api. To achieve this, I only want to have the private key of a delegate address in my ci secrets, no owner private keys. The issue I'm facing is that transactions proposed from a delegate address are not trusted (they appear in the list of transactions provided by the api), and this means that they are not shown in the safe ui under the queue section.

I'm now wondering if:

  • this is expected behavior, but if that's the case then what is the purpose of the delegate address feature
  • I'm doing something wrong when crafting the proposals

This is what I'm currently doing

        const signer = scdk.signers[delegateAddress];
        if (signer === undefined) {
            throw new Error(`Signer for delegate ${proposalConfig.delegate} not loaded, please provide private key`);
        const safeKit = await getSafeKit(signer, safe.address);
        const safeTx = await safeKit.createTransaction({
            safeTransactionData: {
                to: utils.getAddress(txs[0].transaction.to),
                value: "0x00",
                data: txs[0].transaction.data,
                operation: 0
        const hash = await safeKit.getTransactionHash(safeTx);
        const signature = await safeKit.signTransactionHash(hash);
        const axiosPayload = {
            safe: utils.getAddress(safe.address),
            to: safeTx.data.to,
            value: 0,
            data: safeTx.data.data,
            operation: safeTx.data.operation,
            gasToken: safeTx.data.gasToken,
            safeTxGas: safeTx.data.safeTxGas,
            baseGas: safeTx.data.baseGas,
            gasPrice: safeTx.data.gasPrice,
            refundReceiver: safeTx.data.refundReceiver,
            nonce: safeTx.data.nonce,
            contractTransactionHash: hash,
            sender: utils.getAddress(delegateAddress),
            signatures: signature.data
        const res = await axios.post(`https://safe-transaction-goerli.safe.global/api/v1/safes/${utils.getAddress(safe.address)}/multisig-transactions/`,

where safeKit is an instance of import Safe from '@safe-global/protocol-kit' and the signer is an ethers.Signer instance.

The requests results in a 201 and I can see the proposal in the list of transactions, but trusted: false.

Am I doing something wrong ?

1 Answer 1


Managed to make it work by using the safe transaction sdk

const proposeTxPayload = {
    safeAddress: utils.getAddress(safe.address),
    safeTransactionData: safeTx.data,
    safeTxHash: hash,
    senderAddress: utils.getAddress(delegateAddress),
    senderSignature: signature.data

await safeApiWithSigner.proposeTransaction(proposeTxPayload);

The sdk returned an error for a long time, saying the payload was "unprocessable", the reason was that the value field was an hex string, changing it to a decimal string made it work (so be careful about data formatting in the tx payload for this call to work)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.