Documentation Index Fetch the complete documentation index at: https://mintlify.com/snapshot-labs/sx-monorepo/llms.txt
Use this file to discover all available pages before exploring further.
Authenticators verify that governance actions (proposals, votes, updates) are authorized by the correct user. They act as the entry point for all user interactions with a space.
What are Authenticators?
An authenticator is a contract that:
Verifies user identity - Confirms the action comes from the claimed address
Validates signatures or transactions - Checks cryptographic proofs
Calls the space contract - Forwards the authenticated action
Each space can enable multiple authenticators, allowing users to choose their preferred authentication method.
Authenticator Interface
EVM Authenticators
EVM authenticators create contract calls from envelopes:
interface Authenticator {
type : string ;
createCall (
envelope : Envelope < Propose | UpdateProposal | Vote >,
selector : string ,
calldata : string []
) : Call ;
}
type Call = {
abi : ContractInterface ;
args : any [];
};
Starknet Authenticators
Starknet authenticators create separate calls for each action type:
interface Authenticator {
type : string ;
createProposeCall (
envelope : Envelope < Propose >,
args : ProposeCallArgs
) : Call ;
createVoteCall (
envelope : Envelope < Vote >,
args : VoteCallArgs
) : Call ;
createUpdateProposalCall (
envelope : Envelope < UpdateProposal >,
args : UpdateProposalCallArgs
) : Call ;
}
EVM Authenticators
Vanilla Authenticator
The simplest authenticator - uses direct transaction authentication.
function createVanillaAuthenticator () : Authenticator {
return {
type: 'vanilla' ,
createCall (
envelope : Envelope < Propose | UpdateProposal | Vote >,
selector : string ,
calldata : string []
) : Call {
const { space } = envelope . data ;
return {
abi: VanillaAuthenticatorAbi ,
args: [ space , selector , ... calldata ]
};
}
};
}
How it works : The transaction sender is authenticated by the transaction itself. No signature required.
Use case : Direct onchain interaction where the user sends the transaction themselves.
EthTx Authenticator
Authenticates via direct Ethereum transactions with additional validation.
function createEthTxAuthenticator () : Authenticator {
return {
type: 'ethTx' ,
createCall (
envelope : Envelope < Propose | UpdateProposal | Vote >,
selector : string ,
calldata : string []
) : Call {
const { space } = envelope . data ;
return {
abi: EthTxAuthenticatorAbi ,
args: [ space , selector , ... calldata ]
};
}
};
}
How it works : Similar to vanilla, but with additional onchain checks.
Use case : Direct transactions with enhanced validation.
EthSig Authenticator
Authenticates using EIP-712 typed signatures for gasless voting.
function createEthSigAuthenticator (
type : 'ethSig' | 'ethSigV2'
) : Authenticator {
return {
type ,
createCall (
envelope : Envelope < Propose | UpdateProposal | Vote >,
selector : string ,
calldata : string []
) : Call {
const { signatureData , data } = envelope ;
const { space } = data ;
if ( ! signatureData )
throw new Error ( 'signatureData is required for this authenticator' );
const { r , s , v } = getRSVFromSig ( signatureData . signature );
const args = [
v ,
hexPadLeft ( r . toHex ()),
hexPadLeft ( s . toHex ()),
signatureData . message . salt || '0x00' ,
space ,
selector ,
... calldata
];
return {
abi: EthSigAuthenticatorAbi ,
args
};
}
};
}
How it works :
User Signs Message
User signs an EIP-712 typed data message with their wallet
Submit to Relayer
Signature is submitted to Mana relayer or sent directly
Verify Signature
Authenticator contract recovers signer address from signature
Execute Action
If signature is valid, the action is executed on behalf of the signer
EIP-712 message format for voting :
type EIP712VoteMessage = {
space : string ;
voter : string ;
proposalId : number ;
choice : number ;
userVotingStrategies : IndexedConfig [];
voteMetadataURI : string ;
};
Use case : Gasless voting where a relayer pays transaction fees. Users sign messages instead of sending transactions.
Starknet Authenticators
Starknet Vanilla Authenticator
Direct transaction authentication on Starknet.
Use case : Direct Starknet transactions.
StarkSig Authenticator
Authenticates using Starknet signature verification.
function createStarkSigAuthenticator () : Authenticator {
return {
type: 'starkSig' ,
createProposeCall (
envelope : Envelope < Propose >,
args : ProposeCallArgs
) : Call {
const { authenticator } = envelope . data ;
if ( ! envelope . signatureData ?. signature ) {
throw new Error ( 'signature is required for this authenticator' );
}
const compiled = callData . compile ( 'authenticate_propose' , [
envelope . signatureData . signature ,
envelope . data . space ,
envelope . signatureData . address ,
shortString . splitLongString ( args . metadataUri ),
{
address: args . executionStrategy . address ,
params: args . executionStrategy . params
},
args . strategiesParams ,
envelope . signatureData . message . salt
]);
return {
contractAddress: authenticator ,
entrypoint: 'authenticate_propose' ,
calldata: compiled
};
},
createVoteCall ( envelope : Envelope < Vote >, args : VoteCallArgs ) : Call {
const { authenticator } = envelope . data ;
if ( ! envelope . signatureData ?. signature ) {
throw new Error ( 'signature is required for this authenticator' );
}
const compiled = callData . compile ( 'authenticate_vote' , [
envelope . signatureData . signature ,
envelope . data . space ,
envelope . signatureData . address ,
uint256 . bnToUint256 ( args . proposalId ),
getChoiceEnum ( args . choice ),
args . votingStrategies . map ( strategy => ({
index: strategy . index ,
params: strategy . params
})),
shortString . splitLongString ( args . metadataUri )
]);
return {
contractAddress: authenticator ,
entrypoint: 'authenticate_vote' ,
calldata: compiled
};
},
createUpdateProposalCall (
envelope : Envelope < UpdateProposal >,
args : UpdateProposalCallArgs
) : Call {
const { authenticator } = envelope . data ;
if ( ! envelope . signatureData ?. signature ) {
throw new Error ( 'signature is required for this authenticator' );
}
const compiled = callData . compile ( 'authenticate_update_proposal' , [
envelope . signatureData . signature ,
envelope . data . space ,
envelope . signatureData . address ,
uint256 . bnToUint256 ( args . proposalId ),
{
address: args . executionStrategy . address ,
params: args . executionStrategy . params
},
shortString . splitLongString ( args . metadataUri ),
envelope . signatureData . message . salt
]);
return {
contractAddress: authenticator ,
entrypoint: 'authenticate_update_proposal' ,
calldata: compiled
};
}
};
}
How it works : Similar to EthSig but using Starknet’s native signature verification.
Use case : Gasless voting on Starknet.
StarkTx Authenticator
Authenticates via direct Starknet transactions.
Use case : Direct Starknet transaction authentication.
Cross-Chain Authenticators
Starknet also supports Ethereum authenticators for cross-chain governance:
EthSig on Starknet : Verify Ethereum signatures on Starknet
EthTx on Starknet : Accept Ethereum transaction proofs on Starknet
Signature Data Structure
The SignatureData type contains all information needed to verify a signature:
type SignatureData = {
address : string ; // Signer address
signature ?: string | string []; // Signature bytes
message ?: Record < string , any >; // Signed message
domain ?: TypedDataDomain ; // EIP-712 domain
types ?: Record < string , TypedDataField []>; // EIP-712 types
primaryType ?: string ; // EIP-712 primary type
// For commit-reveal schemes
commitTxId ?: string ; // Commit transaction ID
commitHash ?: string ; // Commit hash
};
Using Authenticators
Enable authenticators when creating a space:
const { address , txId } = await client . deploySpace ({
signer ,
params: {
// ... other params
authenticators: [
'0x...' , // Vanilla authenticator
'0x...' , // EthSig authenticator
'0x...' // EthTx authenticator
]
}
});
Voting with Vanilla Authenticator
Direct transaction (user pays gas):
await client . vote ({
signer ,
envelope: {
data: {
space: '0x...' ,
proposal: 1 ,
choice: Choice . For ,
authenticator: vanillaAuthenticatorAddress ,
strategies: [ ... ],
metadataUri: ''
}
// No signatureData needed
}
});
Voting with EthSig Authenticator
Gasless signature-based voting:
// 1. Create the vote envelope
const envelope = {
data: {
space: '0x...' ,
proposal: 1 ,
choice: Choice . For ,
authenticator: ethSigAuthenticatorAddress ,
strategies: [ ... ],
metadataUri: ''
}
};
// 2. Sign the typed data
const domain = {
name: 'snapshot-x' ,
version: '1' ,
chainId: 1 ,
verifyingContract: envelope . data . space
};
const types = {
Vote: [
{ name: 'space' , type: 'address' },
{ name: 'voter' , type: 'address' },
{ name: 'proposalId' , type: 'uint256' },
{ name: 'choice' , type: 'uint8' },
{ name: 'userVotingStrategies' , type: 'IndexedStrategy[]' },
{ name: 'voteMetadataURI' , type: 'string' }
],
IndexedStrategy: [
{ name: 'index' , type: 'uint8' },
{ name: 'params' , type: 'bytes' }
]
};
const message = {
space: envelope . data . space ,
voter: await signer . getAddress (),
proposalId: envelope . data . proposal ,
choice: envelope . data . choice ,
userVotingStrategies: strategiesParams ,
voteMetadataURI: ''
};
const signature = await signer . _signTypedData ( domain , types , message );
// 3. Submit to relayer or execute directly
const envelopeWithSig = {
... envelope ,
signatureData: {
address: await signer . getAddress (),
signature ,
domain ,
types ,
message
}
};
// Send to Mana relayer
await fetch ( manaUrl , {
method: 'POST' ,
body: JSON . stringify ({
method: 'vote' ,
params: envelopeWithSig
})
});
Authenticator Resolution
Authenticators are resolved from network configuration:
// EVM
function getAuthenticator (
address : string ,
networkConfig : EvmNetworkConfig
) : Authenticator | null {
const authenticator = networkConfig . authenticators [ address ];
if ( ! authenticator ) return null ;
if ( authenticator . type === 'vanilla' ) return createVanillaAuthenticator ();
if ( authenticator . type === 'ethTx' ) return createEthTxAuthenticator ();
if ( authenticator . type === 'ethSig' || authenticator . type === 'ethSigV2' ) {
return createEthSigAuthenticator ( authenticator . type );
}
return null ;
}
// Starknet
function getAuthenticator (
address : string ,
networkConfig : NetworkConfig
) : Authenticator | null {
const authenticator = networkConfig . authenticators [ hexPadLeft ( address )];
if ( ! authenticator ) return null ;
if ( authenticator . type === 'vanilla' ) return createVanillaAuthenticator ();
if ( authenticator . type === 'ethSig' ) return createEthSigAuthenticator ();
if ( authenticator . type === 'ethTx' ) return createEthTxAuthenticator ();
if ( authenticator . type === 'starkSig' ) return createStarkSigAuthenticator ();
if ( authenticator . type === 'starkTx' ) return createStarkTxAuthenticator ();
return null ;
}
Authentication Flow
Authenticator Types by Network
Authenticator EVM Starknet Gasless Description vanilla ✅ ✅ ❌ Direct transaction ethTx ✅ ✅ ❌ Ethereum transaction with validation ethSig ✅ ✅ ✅ EIP-712 signature ethSigV2 ✅ ❌ ✅ Updated EIP-712 signature starkSig ❌ ✅ ✅ Starknet signature starkTx ❌ ✅ ❌ Starknet transaction
Spaces Learn about governance spaces
Strategies Understand voting power
Creating Proposals Create your first proposal
Gasless Voting Implement gasless voting