@glideco/recovery
v0.1.0
Published
Pure encoders for Glide's social-recovery primitives. EVM: Zodiac Delay module deploy + queue + execute + cancel. Solana: Squads v4 config-transaction encoders for owner / threshold changes. Operator brings its own KMS-held signer + chain RPC + DB.
Maintainers
Readme
@glideco/recovery
Pure encoders for Glide's social-recovery primitives. Two surfaces:
- EVM — Safe + Zodiac Delay v1.0.1: deploy + enable the module, queue / execute / cancel a recovery action, read on-chain queue state.
- Solana — Squads v4: compose the propose-triple (config-tx-create + proposal-create + proposal-approve) for an owner / threshold change, plus proposal-cancel + config-transaction-execute helpers.
I/O-free. Every export is a pure function that produces calldata bytes (EVM) or pre-built TransactionInstruction-shaped objects (Solana). Operators bring their own KMS-held signer, RPC client, and DB.
Install
npm install @glideco/recoveryEVM — recovery proposal end-to-end
import {
composeEnableRecoveryModuleCalls,
composeQueueRecoveryCall,
composeExecuteRecoveryCall,
RECOVERY_COOLDOWN_SECONDS,
} from '@glideco/recovery';
// 1. Compute the deterministic Delay-module address + the deploy +
// enable bundle. One user signature on the Safe activates recovery.
const setup = composeEnableRecoveryModuleCalls({
safe: userSafeAddress,
recoverySigner: glideRecoveryEvmAddress, // address of the KMS key
cooldownSeconds: RECOVERY_COOLDOWN_SECONDS, // 72h default
});
// setup = { moduleAddress, deploy: { to, data, value }, enable: { to, data, value } }
// 2. Later, recovery key proposes "rotate to new owner":
const queue = composeQueueRecoveryCall({
delayModule: setup.moduleAddress,
inner: {
to: userSafeAddress,
value: '0',
data: encodeSafeSwapOwnerCalldata(/* ... */),
operation: 0,
},
});
// queue.calldata is what the recovery key signs + broadcasts.
// 3. After 72h cooldown, anyone can execute:
const execute = composeExecuteRecoveryCall({
delayModule: setup.moduleAddress,
inner: queue.inner,
});EVM — read on-chain queue state
import { readDelayModuleQueueState } from '@glideco/recovery';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({ chain: mainnet, transport: http() });
const state = await readDelayModuleQueueState({
client,
delayModule: setup.moduleAddress,
});
// state = { cooldownSeconds, expirationSeconds, queueNonce, txNonce, queue: [...] }Solana — Squads recovery
import {
composeSolanaRecoveryAction,
composeSolanaCancelRecoveryInstruction,
recomposeSolanaExecuteRecoveryInstruction,
} from '@glideco/recovery';
// Propose: "swap_owner" expands to [RemoveMember, AddMember] under the
// hood because Squads v4 has no atomic SwapMember instruction.
const composed = composeSolanaRecoveryAction({
multisigPda,
transactionIndex,
currentThreshold,
creator, // recovery key pubkey (= Seat R)
action: {
kind: 'swap_owner',
oldOwner: '<base58>',
newOwner: '<base58>',
},
});
// composed.instructions = [configTxCreate, proposalCreate, proposalApprove]
// composed.innerTx = serialized actions (persisted in DB for replay)
// User-initiated cancel:
const cancel = composeSolanaCancelRecoveryInstruction({
multisigPda,
transactionIndex,
member: userSeatPubkey,
});
// Execute (after cooldown):
const exec = recomposeSolanaExecuteRecoveryInstruction({
multisigPda,
transactionIndex,
member: anyMemberPubkey,
});Allowlist
The Squads encoder also exports the canonical set of accepted instruction discriminators for the broadcaster's calldata-pinning check:
import {
RECOVERY_ALLOWED_INSTRUCTION_HEX,
extractInstructionDiscriminatorHex,
} from '@glideco/recovery';
for (const ix of tx.instructions) {
const disc = extractInstructionDiscriminatorHex(ix.data);
if (!RECOVERY_ALLOWED_INSTRUCTION_HEX.has(disc)) {
throw new Error(
`recovery tx contains a non-recovery instruction (${disc})`
);
}
}What this package does NOT include
- KMS / env-var / file-based signer loading. Operators build their own (e.g.
ethers.Walletfrom KMS, or@solana/web3.jsKeypair.fromSecretKey). - Database persistence of the multi-step recovery state machine.
- The Inngest cron that ticks queued recoveries through cooldown → execute.
- The router that gates "is the user allowed to start recovery on this vault" — that's policy the operator owns.
These live in apps/web/src/server/lib/user-multisig/ on the source repo as a reference implementation operators can fork.
License
MIT.
