Skip to content

Add Poly Safe multisig#2378

Merged
jmoreira-valory merged 20 commits intomainfrom
feat/add_polymarket_multisigs
Jan 14, 2026
Merged

Add Poly Safe multisig#2378
jmoreira-valory merged 20 commits intomainfrom
feat/add_polymarket_multisigs

Conversation

@jmoreira-valory
Copy link
Collaborator

@jmoreira-valory jmoreira-valory commented Jan 5, 2026

Proposed changes

Add Poly Safe multisig.

Note: The PR contains tox -e check-generate-all-protocols to fix linters, due to change in year, therefore multiple unrelated files have been changed.

The following issues/refactor have been identified for future work:

Fixes

Types of changes

What types of changes does your code introduce? (A breaking change is a fix or feature that would cause existing functionality and APIs to not work as expected.)
Put an x in the box that applies

  • Non-breaking fix (non-breaking change which fixes an issue)
  • Breaking fix (breaking change which fixes an issue)
  • Non-breaking feature (non-breaking change which adds functionality)
  • Breaking feature (breaking change which adds functionality)
  • Refactor (non-breaking change which changes implementation)
  • Messy (mixture of the above - requires explanation!)

Checklist

Put an x in the boxes that apply.

  • I have read the CONTRIBUTING doc
  • I am making a pull request against the main branch (left side). Also you should start your branch off our main.
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have locally run AI agents that could be impacted and they do not present failures derived from my changes
  • Public-facing documentation has been updated with the changes affected by this PR. Even if the provided contents are not in their final form, all significant information must be included.
  • Any backwards-incompatible/breaking change has been clearly documented in the upgrading document.

Further comments

Comment on lines 76 to 77
"poly_safe_creator_with_recovery_module": "0xA749f605D93B3efcc207C54270d83C6E8fa70fF8", # Polymarket multisig WITH recovery module
"poly_safe_same_address_with_recovery_module": "0xBcb1BAC84B5BcAb350C89c50ADc9064eD15a4485", # Polymarket same address multisig WITH recovery module
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kupermind Please check that the snake-case naming of these contracts match with the autonolas registry repository.

@jmoreira-valory jmoreira-valory changed the title Add polymarket multisig Add Poly Safe multisig Jan 9, 2026
"safe_multisig_with_recovery_module": "0x1a0bFCC27051BCcDDc444578f56A4F5920e0E083", # Multisig WITH recovery module
"recovery_module": "0x02C26437B292D86c5F4F21bbCcE0771948274f84", # Same address multisig WITH recovery module
"poly_safe_creator_with_recovery_module": "0xA749f605D93B3efcc207C54270d83C6E8fa70fF8", # Poly Safe multisig WITH recovery module
"poly_safe_same_address_with_recovery_module": "0xBcb1BAC84B5BcAb350C89c50ADc9064eD15a4485", # Poly Safe same address multisig WITH recovery module

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this one used? Or just added for now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be used in middleware, this ABI matches the "Same address multisig WITH recovery module" , right?

Copy link
Collaborator Author

@jmoreira-valory jmoreira-valory Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative: make POLY_SAFE_SAME_ADDRESS_WITH_RECOVERY_MODULE_CONTRACT address = RECOVERY_MODULE_CONTRACT (for symmetry with other contracts), and use the contract 0xBcb1BAC84B5BcAb350C89c50ADc9064eD15a4485 for the very extreme corner cases considered by the team.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be removed for now and added in another PR. for now poly_safe_same_address_with_recovery_module = recovery_module

Comment on lines 112 to 141
create_transaction_hash = registry_contracts.poly_safe_creator_with_recovery_module.get_poly_safe_create_transaction_hash(
ledger_api=ledger_api,
contract_address=contract_address,
)
sig1 = crypto.sign_message(
create_transaction_hash,
is_deprecated_mode=True, # Legacy signature, do not use EIP-191 signing
)
sig1_bytes = bytes.fromhex(sig1[2:])

enable_module_hash = registry_contracts.poly_safe_creator_with_recovery_module.get_enable_module_transaction_hash(
ledger_api=ledger_api,
contract_address=contract_address,
signer_address=crypto.address,
)
sig2 = crypto.sign_message(
enable_module_hash,
is_deprecated_mode=True, # Legacy signature, do not use EIP-191 signing
)
sig2_bytes = bytes.fromhex(sig2[2:])

# Pack both signatures in the format required by PolySafeCreatorWithRecoveryModule.create(...)
r1 = sig1_bytes[0:32]
s1 = sig1_bytes[32:64]
v1 = sig1_bytes[64]
data = eth_abi.encode(
["(uint8,bytes32,bytes32)", "bytes"], [(v1, r1, s1), sig2_bytes]
)

return "0x" + data.hex()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this inside the poly_safe_creator_with_recovery_module contract? Because

  1. it's a logic heavily related to the contract interaction
  2. we avoid making eth_abi a dependency of OA

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we could, but read my comment above: other create deployment data methods are written on this file. This is the equivalent for polysafe. I agree with your comments but we should be consistent with the existing code as well. We can discuss what's the best tradeoff option.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, opened issues #2381 and #2382

)


def get_poly_safe_deployment_payload(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this function used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in middleware. In theory it could be used here as well if we support via CLI create a Poly safe (could be considered as a new feature in another PR).
It has been added here for al"symmetry" with the existing get_XXX_payload methods.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will open an issue

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OjusWiZard
OjusWiZard previously approved these changes Jan 12, 2026
"address": chain_config.rpc,
"chain_id": chain_config.chain_id,
"is_gas_estimation_enabled": True,
"poa_chain": chain_type in (ChainType.OPTIMISM, ChainType.POLYGON),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's define a constant:

POA_CHAINS = frozenset((ChainType.OPTIMISM, ChainType.POLYGON))

and then:

Suggested change
"poa_chain": chain_type in (ChainType.OPTIMISM, ChainType.POLYGON),
"poa_chain": chain_type in POA_CHAINS,

OjusWiZard
OjusWiZard previously approved these changes Jan 12, 2026
Comment on lines 18 to 25
eth-abi:
version: ==5.2.0
open-aea-ledger-ethereum:
version: ==2.0.6
open-aea-test-autonomy:
version: ==0.21.5
web3:
version: <8,>=7.0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some dependencies are not used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove web3

ledger_api=ledger_api, contract_address=contract_address
)
return contract_instance.functions.getEnableModuleTransactionHash(
signer_address
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signer_address could be checksummed for safety.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to return a dictionary from contract methods as per the conventions of the framework.

"address": chain_config.rpc,
"chain_id": chain_config.chain_id,
"is_gas_estimation_enabled": True,
"poa_chain": chain_type in (ChainType.OPTIMISM, ChainType.POLYGON),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's define a constant:

POA_CHAINS = frozenset((ChainType.OPTIMISM, ChainType.POLYGON))

and then:

Suggested change
"poa_chain": chain_type in (ChainType.OPTIMISM, ChainType.POLYGON),
"poa_chain": chain_type in POA_CHAINS,

Comment on lines 251 to 253
POLY_SAFE_SAME_ADDRESS_WITH_RECOVERY_MODULE_CONTRACT = PublicId.from_str(
"valory/poly_safe_same_address_with_recovery_module"
) # Poly Safe same address multisig WITH recovery module
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this exist?

Copy link
Collaborator Author

@jmoreira-valory jmoreira-valory Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not exist, but some other contract packages do not exist either and use their placeholders here, for example gnosis_safe_same_address_multisig. In this case, these placeholders are used in the library to retrieve the address only, not to use the contract package. (their contract implementation is identical to other contracts)

contract_address=contract_address,
crypto=crypto,
)
return "0x" + data.hex()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could return the data with the prefix from the contract.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but since the other methods of the contract return bytes (to avoid having to parse and unparse '0x' strings), for consistency this method also returns bytes . I am unsure wha'ts the standard approach here, as I haven't seen an homogeneous criteria for returning outputs in the contracts...

Co-authored-by: Ojuswi Rastogi <55619686+OjusWiZard@users.noreply.github.com>
@jmoreira-valory jmoreira-valory merged commit 4b7be5d into main Jan 14, 2026
14 of 16 checks passed
@jmoreira-valory jmoreira-valory deleted the feat/add_polymarket_multisigs branch January 14, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants