Using Gnosis Safe Ethereum Multisig in Rust

After dealing with some frustrations with native t-ECDSA libraries, we decided to switch to Gnosis Safe backed multi-sig contracts for Ethereum integrations. While native t-ECDSA is more general purpose, and cheaper (in the sense that it doesn't require a $30-100+ deployment in fees per contract..) existing Rust implementations have some irritating complications worth another post.
When looking for Rust SDKs, the first google search was disappointing to say the least:

Safe doesn't appear to have 'official' RUST ABI client level support, mainly seeming to prefer TypeScript / web3 style bindings, and/or their python CLI client. All of these libraries seem relatively safe (hah) so to speak, and well documented / supported / written, and essentially act as a business logic wrapper layer around the raw Ethereum ABI. They primarily exist to act as a 'safe' reference proxy factory contract implementation, allowing you to deploy a new contract instance without ever touching Remix / Solidity code.

So rather than searching through GitHub repos looking for a trustworthy unofficial rust client implementation, it seemed easier to start looking at the Safe CLI. The other 'easy' option would be running a sidecar node service alongside our main service, and interfacing that way, but that adds a fair amount of operational / deployment complexity, and didn't seem to be worth the effort if binding a CLI is sufficient. Their CLI has a convenient docker image, and considering how lightweight the demands of these client interactions are, it's overkill to run a service when a simple shell out would suffice.
That brings us to:

The Safe CLI docs. Their organization is a little confusing, and you immediately hit this declaration of multiple modes, which really doesn't help clarify things at all at this point. (Later finding out, they have a managed API service for helping translate contract emitted events, for which their clients automatically integrate.)

The main useful commands you'll get to are all listed https://docs.safe.global/advanced/cli-reference/common-commands here under the reference 'common commands'.
safe-creator <node_url> <private_key> --owners <checksummed_address_1> <checksummed_address_2> --threshold <uint> --salt-nonce <uint256>
Immediately, this is sort of obvious how to use, and it was pretty straightforward to bind in Rust. The node URL is an RPC url, the private key is a standard hex, owners are string addresses, and so on. Salt nonce doesn't appear to be required at all as it is requested from the RPC by default through their ABI.

This leads us to a rather simple binding (although later realizing you can improve this by running it in non-interactive mode,) it only took a few minutes to get this up and running, and it spits out a nice formatted string showing your deployed contract address. Very easy to test out on Sepolia RPC with some quick faucet funds.
So naturally, after creating a multi-sig contract, the next obvious thing to test is having one keypair sign a portion of a transaction. You'd think it'd be easy, just go to the doc reference page and bind the command, so you start writing some bindings.

And then check the docs and find out that:

None of these commands will work with only a single key. You need to supply all the private hexes, at the same time. It wasn't entirely clear from their immediate docs and commands that this was the case, although I'm sure it's buried somewhere in the references / help strings. It didn't take long to figure out this was a dead end.
That leaves the options for getting contract interactions with either their TypeScript / Node SDK, either through a sidecar service or node wrapped CLI – OR attempting to write my own ABI bindings (since most of the references on GitHub were obsolete or unapproved.)
It made the most sense to see if it was a low effort involved in getting the ABI bindings working correctly, before having a fallback of giving up and succumbing to Node.js, thankfully, it ended up working out.
First, it was required to locate the official / latest ABI json specification: https://github.com/safe-global/safe-deployments/tree/main/src/assets/v1.4.1
Then you need to ensure you have foundry
installed with cargo install foundry
Next: generate the Rust ABI bindings: cast bind --crate-name safe-bindings keys/src/eth/safe.json
and rename the module as appropriate for your build. This generates a rather large ridiculous code-gen Rust file like so:

Autocomplete doesn't work super well here without extra steps, so it's recommended to just search for the methods you're looking to interact with. In our case, we need the initial transaction hash for signing:

The nice thing about interacting with their ABI, is that transaction hashes are deterministic. You don't actually need to pass around the encoded internal transaction types all over the place, since you can just supply the same parameters and it'll regenerate the same thing elsewhere in the code, which is actually quite convenient.
Next up, figuring out how to sign this properly.

Fortunately, the same Foundry / Alloy / ethers 2.0 crates provide all the necessary signing functions, but the most unclear part was the exact requirements for signing the hashes was. Naturally, this information is 'sort of' available in their docs, but certainly not easy to find. It's embedded mostly in the client library specifications, which is annoying, so we just figured it out through trial and error mostly.

Next up, the logic for combining signatures. Now, this makes sense, but it's definitely a complex business logic client side replication of something you might expect an ABI to handle. I don't blame them for not implementing ABI methods to do this, because fee's are outrageously expensive, but still, it was a bit of trial and error to ensure this was the right method. So – in here, we're adding a recovery step to make this slightly 'simpler' in the communication overhead operation (only the signature is required, you don't need to preserve the address separately and keep track of the mappings, because you'd need to reverify it anyways so.)
First you need to recover the address (importantly, the recovery hash type data), then SORT the signatures by address (to create a sort of 'deterministic' multi-sig operation. Only then do you combine them, not by constructing a new data structure of course, but by just concatenating them obviously.

Finally, you combine them and invoke exec_transaction. Again, notice how we didn't need to pass around the transaction data, super convenient feature of the contract in that sense. It also allows you to just ignore fees and supply it through the caller (the max gas of 150k, well over the actual 'used amount' of ~70k gas or so.) This was extremely nice, to avoid decoding any data from the contract itself.
From here, you can monitor the events of the contract through the typical Etherscan / foundry block explorer clients, and determine your actions based on that. Hopefully this guide will help someone out in the future attempting to use their ABI, and shed some light into the development process for Rust.