Add script for adding gnosis safe delegates (#1032)

pull/1037/head
Asa Oines 2 years ago committed by GitHub
parent edbf197d51
commit e71ac4a5c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      typescript/infra/package.json
  2. 70
      typescript/infra/scripts/safe-delegate.ts
  3. 53
      typescript/infra/src/utils/safe.ts
  4. 14
      typescript/sdk/src/consts/chainMetadata.ts
  5. 974
      yarn.lock

@ -11,6 +11,10 @@
"@aws-sdk/client-kms": "3.48.0",
"@aws-sdk/client-s3": "^3.74.0",
"@ethersproject/experimental": "^5.6.2",
"@ethersproject/hardware-wallets": "~5.6",
"@gnosis.pm/safe-core-sdk": "^2.3.2",
"@gnosis.pm/safe-ethers-lib": "^1.4.0",
"@gnosis.pm/safe-service-client": "^1.2.0",
"@nomiclabs/hardhat-etherscan": "^3.0.3",
"asn1.js": "5.4.1",
"aws-kms-ethers-signer": "^0.1.3",

@ -0,0 +1,70 @@
import { LedgerSigner } from '@ethersproject/hardware-wallets';
// Due to TS funkiness, the following needs to be imported in order for this
// code to build, but needs to be removed in order for the code to run.
import '@ethersproject/hardware-wallets/thirdparty';
import { SafeDelegateConfig } from '@gnosis.pm/safe-service-client';
import yargs from 'yargs';
import { AllChains } from '@abacus-network/sdk';
import { getSafeDelegates, getSafeService } from '../src/utils/safe';
import { getCoreEnvironmentConfig, getEnvironment } from './utils';
function getArgs() {
return yargs(process.argv.slice(2))
.describe('chain', 'chain of the validator to inspect')
.choices('chain', AllChains)
.demandOption('chain')
.describe('action', 'add or remove')
.choices('action', ['add', 'remove'])
.demandOption('action')
.describe('delegate', 'address of the delegate')
.demandOption('delegate')
.string('delegate')
.describe('safe', 'address of the safe')
.demandOption('safe')
.string('safe').argv;
}
async function delegate() {
const environment = await getEnvironment();
const config = getCoreEnvironmentConfig(environment);
const { chain, delegate, safe, action } = await getArgs();
const multiProvider = await config.getMultiProvider();
const connection = multiProvider.getChainConnection(chain);
const safeService = getSafeService(chain, connection);
const delegates = await getSafeDelegates(safeService, safe);
// Ledger Live derivation path, vary by changing the index i.e.
// "m/44'/60'/{CHANGE_ME}'/0/0";
const path = "m/44'/60'/0'/0/0";
const signer = new LedgerSigner(undefined, 'hid', path);
console.log('Connected to signer with address:', await signer.getAddress());
const delegateConfig: SafeDelegateConfig = {
safe,
delegate,
signer,
label: 'delegate',
};
const baseDescription = `${delegate} as a delegate for ${chain} safe at address ${safe}`;
if (action === 'add') {
console.log(`Adding ${baseDescription}`);
if (delegates.includes(delegate))
throw new Error(`${delegate} is already a delegate`);
await safeService.addSafeDelegate(delegateConfig);
} else if (action === 'remove') {
console.log(`Removing ${baseDescription}`);
if (!delegates.includes(delegate))
throw new Error(`${delegate} is not a delegate`);
await safeService.removeSafeDelegate(delegateConfig);
} else {
throw new Error('unsupported action');
}
}
delegate().then(console.log).catch(console.error);

@ -0,0 +1,53 @@
import Safe from '@gnosis.pm/safe-core-sdk';
import EthersAdapter from '@gnosis.pm/safe-ethers-lib';
import SafeServiceClient from '@gnosis.pm/safe-service-client';
import { ethers } from 'ethers';
import { ChainConnection, ChainName, chainMetadata } from '@abacus-network/sdk';
export function getSafeService(
chain: ChainName,
connection: ChainConnection,
): SafeServiceClient {
const signer = connection.signer;
if (!signer) throw new Error(`no signer found for ${chain}`);
const ethAdapter = new EthersAdapter({ ethers, signer });
const txServiceUrl = chainMetadata[chain].gnosisSafeTransactionServiceUrl;
if (!txServiceUrl)
throw new Error(`must provide tx service url for ${chain}`);
return new SafeServiceClient({ txServiceUrl, ethAdapter });
}
export function getSafe(
connection: ChainConnection,
safeAddress: string,
): Promise<Safe> {
const signer = connection.signer;
if (!signer) throw new Error(`no signer found`);
const ethAdapter = new EthersAdapter({ ethers, signer });
return Safe.create({
ethAdapter,
safeAddress: safeAddress,
});
}
export async function getSafeDelegates(
service: SafeServiceClient,
safe: string,
) {
const delegateResponse = await service.getSafeDelegates(safe);
return delegateResponse.results.map((r) => r.delegate);
}
export async function canProposeSafeTransactions(
proposer: string,
chain: ChainName,
connection: ChainConnection,
safeAddress: string,
): Promise<boolean> {
const safeService = getSafeService(chain, connection);
const safe = await getSafe(connection, safeAddress);
const delegates = await getSafeDelegates(safeService, safeAddress);
const owners = await safe.getOwners();
return delegates.includes(proposer) || owners.includes(proposer);
}

@ -11,6 +11,8 @@ export type ChainMetadata = {
// The CoinGecko API expects, in some cases, IDs that do not match
// ChainNames.
gasCurrencyCoinGeckoId?: string;
// URL of the gnosis safe transaction service.
gnosisSafeTransactionServiceUrl?: string;
};
/**
@ -31,29 +33,37 @@ export interface RpcPagination {
export const celo: ChainMetadata = {
id: 0x63656c6f, // b'celo' interpreted as an int
finalityBlocks: 0,
gnosisSafeTransactionServiceUrl:
'https://transaction-service.gnosis-safe-staging.celo-networks-dev.org',
};
export const ethereum: ChainMetadata = {
id: 0x657468, // b'eth' interpreted as an int
finalityBlocks: 20,
gnosisSafeTransactionServiceUrl: 'https://safe-transaction.gnosis.io',
};
export const arbitrum: ChainMetadata = {
id: 0x617262, // b'arb' interpreted as an int
finalityBlocks: 0,
gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas
gnosisSafeTransactionServiceUrl:
'https://safe-transaction.arbitrum.gnosis.io/',
};
export const optimism: ChainMetadata = {
id: 0x6f70, // b'op' interpreted as an int
finalityBlocks: 0,
gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas
gnosisSafeTransactionServiceUrl:
'https://safe-transaction.optimism.gnosis.io/',
};
export const bsc: ChainMetadata = {
id: 0x627363, // b'bsc' interpreted as an int
finalityBlocks: 15,
gasCurrencyCoinGeckoId: 'binancecoin',
gnosisSafeTransactionServiceUrl: 'https://safe-transaction.bsc.gnosis.io/',
};
export const avalanche: ChainMetadata = {
@ -65,6 +75,8 @@ export const avalanche: ChainMetadata = {
from: 6765067,
},
gasCurrencyCoinGeckoId: 'avalanche-2',
gnosisSafeTransactionServiceUrl:
'https://safe-transaction.avalanche.gnosis.io/',
};
export const polygon: ChainMetadata = {
@ -76,6 +88,8 @@ export const polygon: ChainMetadata = {
from: 19657100,
},
gasCurrencyCoinGeckoId: 'matic-network',
gnosisSafeTransactionServiceUrl:
'https://safe-transaction.polygon.gnosis.io/',
};
/**

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save