Signing quorum currently covers message signing on all supported chains and transaction signing on EVM and Solana. Bitcoin transactions are not covered. For the quorum model itself, the approval lifecycle, and the requirement fields, see Approvals.
approvalRequirements is a PolicyRules field. It composes with other rules in the same policy layer and uses the Account-Layer, Wallet-Layer, and Signer-Layer scope of that layer.
The policy layer can further restrict the applicable chain and chain IDs.
Create a transaction quorum rule
The following rule requires one approval from a Business Account member with theadmin role for native-token transfers of at least 1 ETH on Ethereum mainnet.
- JavaScript
admin, or custom Business Account roles. A member holding any listed role can approve. A value such as anyAdmin works only when the Business Account defines a custom role with that exact name.
Requirement fields
getPolicy returns approvalRequirements with the same authoring shape. Approval progress is reported during signing. It is not part of the policy definition.
Limit a rule by transaction value
Usewhen.minValue to apply the requirement at or above a transaction value. Conditions in the same when object are combined with AND logic.
amountis an integer string in the asset’s smallest unit.- Omit
assetfor the chain’s native token. - An asset address selects that token.
The signing intent
Every quorum-gated signing request produces aSigningIntent: a signed, complete description of the operation that approvers review and that the resume step executes. The SDK prepares and signs it automatically when you initiate signing, and it is stored on the proposal so approvers see the same object the initiator signed.
Intent properties
Operation kinds
operation.kind discriminates the union and determines the remaining fields:
For
message, typedData, and raw intents, payload is the exact prepared payload the signature covers. For EVM transaction intents, limits instead carries the maxGas, maxFeePerGas, maxPriorityFeePerGas, and maxTotalFee ceilings described in Transaction fields the intent binds. A Solana transaction intent carries the compiled message itself, described in Solana transactions.
Complete a quorum-gated signing operation
The signing-quorum flow has three stages:- The initiator attempts to sign. The SDK prepares and signs an operation-bound intent automatically.
- Other eligible Business Account members review and approve the resulting proposal.
- After every requirement is satisfied, the initiator resumes the proposal to produce the signature.
1. Start signing
Use a Business Account wallet with its standard signing methods, such assignMessage, signRawMessage, signTypedData, or signSerializedTransaction. When a matching quorum rule requires approval, the call rejects with SigningConsentRequiredError. Save its proposalId so you can find the proposal later.
- JavaScript
reason tells you what consent is still missing:
APPROVALS_REQUIRED: a proposal opened and needs other members’ approvals. The error carriesproposalIdandapprovalRequirements.INTENT_REQUIRED: the operation’s intent still needs your session-key signature. This is rare because the SDK signs the intent automatically, but it can occur when the local client cannot produce the signature.
INTENT_REQUIRED, sign the returned intent and retry the same call with the signed approvalQuorum. Pass the operation’s stored payload as preparedPayload for message, typed data, and raw signing so the intent is checked against your request before it is signed.
- JavaScript
approvalQuorum for this retry, including signPsbt on Bitcoin and signSerializedTransaction on EVM. Omit it on the first call: populate it only in response to an INTENT_REQUIRED outcome, with the exact intent that was signed.
Transaction fields the intent binds
For an EVM transaction, approvers consent to the stable fields of the operation:from, to, value, data, chainId, and nonce. Those fields cannot change between the approved intent and the signed transaction.
Only the gas and fee values may move, because a proposal can sit pending for as long as its completion window while gas prices drift. The intent carries four ceilings that bound how much the signed transaction may spend on gas:
When you initiate signing,
signSerializedTransaction defaults these limits to the values your unsigned transaction carries. Pass refreshLimits to approve wider ceilings up front, for example padded estimates, so the resumed signature still fits current network conditions after the proposal is approved:
Solana transactions
Start a Solana transaction by callingsignTransaction from @dynamic-labs-sdk/solana with a Business Account wallet. The SDK prepares and signs the intent automatically, and the call rejects with SigningConsentRequiredError when a quorum rule requires approval, as it does for EVM.
operation carries the fee payer, the message version ('legacy' or 'v0'), the message header, the static account keys, and every instruction with its program, accounts, and hex-encoded data. Compute-unit limits and priority fees are instructions, so they are bound too, and none of these can change between approval and signing.
The SDK refuses to prepare an intent for a message it cannot bind completely. The message must be complete and unsigned, the wallet must be its fee payer, and it must not use address lookup tables or a durable nonce.
Refreshable fields
A proposal can wait for approvals for as long as its completion window, which is far longer than some chain values stay valid. If the intent bound those values at initiation, an approved proposal could become impossible to sign. The intent instead carries a signedlifetime marker that names the one value the SDK replaces with a current value when you resume. Because the marker is stored in the intent, an approver who inspects it can see which value will change.
Every Solana transaction intent carries
lifetime: { refreshable: 'blockhash' }. An EVM intent carries lifetime: { refreshable: 'nonce' } only when it was signed with a refreshable nonce. The intents that signSerializedTransaction prepares pin the nonce and have no lifetime. Message, typed data, raw, and EIP-7702 intents never have one.
When you resume, the SDK reads the replacement value from the network the intent was approved for. For Solana it fetches the latest blockhash. For an EVM intent with a refreshable nonce it reads the account’s pending transaction count, so the nonce follows any transaction the account already sent. The SDK then rebuilds the transaction from the approved operation and signs it. The refreshed value is the only difference from what was approved, apart from EVM gas and fee values that stay within the approved limits. If the active network differs from the intent’s chainId, the resume throws SigningProposalNetworkMismatchError instead of reading a value from the wrong chain.
EVM gas and fee values are separate from lifetime. They are the limits ceilings described in Transaction fields the intent binds, and Solana intents have no limits.
2. Approve the proposal
An eligible Business Account member other than the initiator approves the proposal from their own authenticated session.- JavaScript
- React
satisfied value is authoritative. Do not infer readiness from approved >= required because a requirement can also require specific roles.
An approver can inspect the operation before approving it. getBusinessAccountSigningIntent reads the signed intent stored on a signing proposal, so your UI can show the message, typed data, or transaction the quorum is consenting to.
- JavaScript
status: 'unsupported' for proposals that are not signing operations and status: 'mismatch' when the stored intent does not match its proposal.
3. Resume the approved operation
Read the pending proposal again, verify that every requirement is satisfied, and resume it with the same Business Account wallet.resumeSigningProposal consumes the proposal and returns the signature. For an EVM transaction intent the signature is the serialized signed transaction, which you then broadcast yourself. For a Solana transaction intent the result is the hex-encoded signature alone, computed over a message that uses the latest blockhash the SDK fetched at resume time. The SDK does not return that blockhash, so the result is not a broadcastable Solana transaction.
- JavaScript
- React
resumeSigningProposal verifies that the proposal is pending and fully approved and that the selected wallet belongs to the proposal’s Business Account and matches the intent address. It throws a dedicated error when the proposal cannot be resumed:
SigningProposalNotPendingErrorwhen the proposal already left the pending state (executed, expired, or vetoed).SigningProposalApprovalsRequiredErrorwhen its approvals are missing or unmet.SigningProposalIntentUnavailableErrorwhen it has no valid stored intent.SigningProposalWalletMismatchErrorwhen the wallet belongs to another business account; pass thewalletAccountthe intent names.SigningProposalNetworkMismatchErrorwhen the wallet’s active network differs from the network the transaction intent was approved for, for a Solana transaction or an EVM transaction with a refreshable nonce.