Разработка интерфейса управления Safe{Wallet}
Safe{Wallet} (бывший Gnosis Safe) — стандарт de facto для корпоративных и DAO трежури. Стандартный интерфейс на app.safe.global покрывает 80% сценариев, но как только появляются требования специфичные для протокола — кастомные Safe Apps, batch транзакции через Safe Transaction Builder, интеграция с governance модулями или кастомные guards — нужен собственный интерфейс.
Safe SDK: архитектура интеграции
Safe Protocol Kit
Основной инструмент для управления Safe через TypeScript:
import Safe, { EthersAdapter } from '@safe-global/protocol-kit';
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
const safeSdk = await Safe.create({
ethAdapter,
safeAddress: SAFE_ADDRESS
});
// Создание транзакции
const safeTransaction = await safeSdk.createTransaction({
transactions: [{
to: TOKEN_CONTRACT,
value: '0',
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [recipient, amount]
})
}]
});
// Подписание
const signedTransaction = await safeSdk.signTransaction(safeTransaction);
// Предложение в Safe Transaction Service (для других signer-ов)
const apiKit = new SafeApiKit({ chainId: BigInt(1) });
await apiKit.proposeTransaction({
safeAddress: SAFE_ADDRESS,
safeTransactionData: signedTransaction.data,
safeTxHash: await safeSdk.getTransactionHash(signedTransaction),
senderAddress: await signer.getAddress(),
senderSignature: signedTransaction.signatures.get(signer.address.toLowerCase())!.data
});
Safe Transaction Service хранит pending транзакции off-chain и позволяет другим владельцам Safe найти их и подписать без прямой координации.
Batch транзакции через MultiSend
Одна из главных причин использовать Safe — batch: несколько операций в одной транзакции. В стандартном интерфейсе это Transaction Builder. В кастомном:
// Batch: approve + stake в одной транзакции
const batchTransactions = [
{
to: USDC_ADDRESS,
value: '0',
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [STAKING_CONTRACT, parseUnits('10000', 6)]
})
},
{
to: STAKING_CONTRACT,
value: '0',
data: encodeFunctionData({
abi: stakingAbi,
functionName: 'deposit',
args: [parseUnits('10000', 6)]
})
}
];
const safeTransaction = await safeSdk.createTransaction({ transactions: batchTransactions });
MultiSend контракт (деплоен Safe командой, адреса зафиксированы по сетям) исполняет все операции атомарно. Если одна revert — вся batch откатывается.
Ключевые UI компоненты
Список pending транзакций
Центральный элемент интерфейса. Каждая транзакция показывает:
- Тип операции (transfer, contract interaction, batch)
- Decoded calldata — не raw hex, а человекочитаемое описание (
Transfer 5,000 USDC to 0x1234...) - Статус сигнатур:
2/3 confirmationsс аватарами подписантов - Оценка gas
- Кнопки: Sign / Execute (если порог собран) / Reject
Декодирование calldata — через viem decodeFunctionData + ABI репозиторий (4byte.directory или локальный registry known ABIs проекта). Нераспознанные вызовы показываются как hex с предупреждением.
Форма создания транзакции
Для non-technical пользователей (например, финансовый директор DAO) важна форма с абстракцией над raw calldata:
// Форма для DeFi операций — без ввода calldata вручную
function TransactionForm() {
const [operation, setOperation] = useState<'transfer' | 'stake' | 'vote'>();
return (
<form>
<Select onValueChange={setOperation}>
<SelectItem value="transfer">Перевод токенов</SelectItem>
<SelectItem value="stake">Стейкинг в протокол</SelectItem>
<SelectItem value="vote">Голосование в governance</SelectItem>
</Select>
{operation === 'transfer' && <TransferForm />}
{operation === 'stake' && <StakingForm />}
{operation === 'vote' && <VotingForm />}
</form>
);
}
Каждый operation-specific модуль знает ABI соответствующего контракта и собирает calldata самостоятельно. Пользователь вводит понятные значения (адрес, количество токенов).
Управление владельцами и порогом
Изменение owners или threshold — это тоже Safe транзакция (вызов addOwnerWithThreshold, removeOwner, changeThreshold). Интерфейс должен это показывать явно:
- Текущие owners с ENS именами (если резолвятся)
- Текущий threshold
- Форма добавления/удаления owner — создаёт Safe транзакцию требующую M-of-N подпись
- История изменений owners из on-chain событий
Safe Apps iframe интеграция
Safe App — это веб-приложение, работающее внутри iframe Safe интерфейса. Для кастомного интерфейса можно либо встроить существующие Safe Apps (Uniswap, Aave, Compound), либо создать собственные:
import { useSafeAppsSDK } from '@safe-global/safe-apps-react-sdk';
// Внутри Safe App iframe
function SafeAppComponent() {
const { sdk, safe } = useSafeAppsSDK();
async function sendTransaction() {
// Транзакция не требует wallet — отправляется через Safe SDK
const { safeTxHash } = await sdk.txs.send({
txs: [{
to: CONTRACT_ADDRESS,
value: '0',
data: calldata
}]
});
console.log('Proposed:', safeTxHash);
}
}
Delegates и WalletConnect
Delegates — адреса, которым Safe делегирует право предлагать транзакции (но не подписывать). Полезно для автоматизированных систем, которые создают транзакции по расписанию (выплата grants, rebalancing).
WalletConnect v2 в контексте Safe: Safe может действовать как WalletConnect peer — подключается к внешнему dApp и подписывает транзакции через Safe flow. Полезно для работы с протоколами, у которых нет Safe App.
Стек разработки
Next.js 14 + TypeScript, @safe-global/protocol-kit, @safe-global/api-kit, @safe-global/safe-apps-react-sdk, wagmi 2.x + viem для wallet connection и chain interaction, @tanstack/react-query для кэширования данных Safe Transaction Service.
Деплой: Vercel или статичный хостинг. Для внутреннего инструмента DAO — self-hosted на собственном домене с authentication (Privy или custom JWT).
Ориентиры по срокам
Кастомный интерфейс для конкретного Safe с batch транзакциями, списком pending и декодированием calldata для известных контрактов — 3-4 дня. С Safe Apps iframe, delegation управлением, governance интеграцией и полной историей операций — 1-2 недели.







