Admin API Reference
The Admin API allows you to programmatically create new Admin proposals.
Requests need to be authenticated with a bearer token, which is negotiated from the Team API Key with the corresponding capability. Refer to the authentication section for info on how to negotiate it.
We recommend you use the defender-admin-client npm package for simplifying interactions with the Admin API. |
It is not recommended to use the defender-admin-client npm package in a browser environment as sensitive keys would be exposed publicly. |
Proposals Endpoint
The /proposals
endpoint is used for creating new Admin action proposals via a POST
request. Any actions created this way will have no approvals initially. If the recipient contract of the proposal does not exist, it will be created with the parameters provided.
type Network =
| 'mainnet'
| 'goerli'
| 'sepolia'
| 'xdai'
| 'sokol'
| 'fuse'
| 'bsc'
| 'bsctest'
| 'fantom'
| 'fantomtest'
| 'moonbase'
| 'moonriver'
| 'moonbeam'
| 'matic'
| 'mumbai'
| 'avalanche'
| 'fuji'
| 'optimism'
| 'optimism-kovan'
| 'optimism-goerli'
| 'arbitrum'
| 'arbitrum-nova'
| 'arbitrum-rinkeby'
| 'arbitrum-goerli'
| 'celo'
| 'alfajores'
| 'harmony-s0'
| 'harmony-test-s0'
| 'aurora'
| 'auroratest'
| 'zksync'
| 'zksync-goerli'
| 'base-goerli'
| 'linea-goerli';
type Address = string;
type ProposalType = ProposalStepType | ProposalBatchType;
type ProposalStepType = 'upgrade' | 'custom' | 'pause' | 'send-funds' | 'access-control';
type ProposalBatchType = 'batch';
type Hex = string;
type BigUInt = string | number;
type ProposalFunctionInputsArray = (string | boolean)[];
type ProposalFunctionInputs = (
| string
| boolean
| (string | boolean | (string | boolean | (string | boolean | ProposalFunctionInputsArray)[])[])[]
)[];
interface CreateProposalRequest {
contract: PartialContract | PartialContract[];
title: string;
description: string;
type: ProposalType;
via?: Address;
viaType?: 'EOA' | 'Gnosis Safe' | 'Gnosis Multisig';
functionInterface?: ProposalTargetFunction;
functionInputs?: ProposalFunctionInputs;
metadata?: ProposalMetadata;
steps?: ProposalStep[];
}
interface PartialContract {
network: Network;
address: Address;
name?: string;
abi?: string;
}
interface ProposalMetadata {
newImplementationAddress?: Address;
newImplementationAbi?: string;
proxyAdminAddress?: Address;
action?: 'pause' | 'unpause' | 'grantRole' | 'revokeRole';
operationType?: 'call' | 'delegateCall';
account?: Address;
role?: Hex;
sendTo?: Address;
sendValue?: BigUInt;
sendCurrency?: Token | NativeCurrency;
}
interface Token {
name: string;
symbol: string;
address: Address;
network: Network;
decimals: number;
type: 'ERC20';
}
interface NativeCurrency {
name: string;
symbol: string;
decimals: number;
type: 'native';
}
interface ProposalStep {
contractId: string;
targetFunction?: ProposalTargetFunction;
functionInputs?: ProposalFunctionInputs;
metadata?: ProposalMetadata;
type: ProposalStepType;
}
interface ProposalTargetFunction {
name?: string;
inputs?: ProposalFunctionInputType[];
}
interface ProposalFunctionInputType {
name?: string;
type: string;
internalType?: string;
components?: ProposalFunctionInputType[];
}
Note that the fields via
(address of the multisig via which the request is sent), viaType
(type of the multisig), functionInterface
(ABI definition of the function to call), and functionInputs
are required for custom
proposals. On the other hand, only metadata.newImplementationAddress
is required for upgrade
type proposals, since Defender will automatically calculate the remaining fields for you.
An example of an upgrade request:
DATA='{
"contract": {
"address": "0x179810822f56b0e79469189741a3fa5f2f9a7631",
"network": "rinkeby"
},
"title": "Upgrade to v2",
"description": "Upgrading contract to version 2.0",
"type": "upgrade",
"metadata": {
"newImplementation": "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9",
"newImplementationAbi": "[{\"inputs \": [],\"name \": \"greet \",\"outputs \": [{\"internalType \": \"string \",\"name \": \"\",\"type \": \"string \"}],\"stateMutability \": \"pure \",\"type \": \"function \"}]"
}
}'
curl \
-X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d "$DATA" \
"https://defender-api.openzeppelin.com/admin/proposals"
The Defender API will only validate that the function inputs are valid with regards to the signature, but it will not validate that the proposal can actually be executed. This means you can create proposals for calling a non-existing function on a contract, or trying to upgrade a non-upgradeable contract. However, you will not be able to approve them afterwards. |
Archiving Proposals
Proposals can be archived (and unarchived) via API calls by passing a boolean archived
value in the PUT call payload:
DATA='{
"archived": true
}'
curl \
-X PUT \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d "$DATA" \
"https://defender-api.openzeppelin.com/admin/contracts/${CONTRACT_ID}/proposals/${PROPOSAL_ID}/archived"
The same call can be made using defender-admin-client
:
await client.archiveProposal(contractId, proposalId);
await client.unarchiveProposal(contractId, proposalId);
Retrieve a Proposal
Proposals can be retrieved via the API:
curl \
-X GET \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d "$DATA" \
"https://defender-api.openzeppelin.com/admin/contracts/${CONTRACT_ID}/proposals/${PROPOSAL_ID}"
The same call can be made using defender-admin-client
passing the contract and proposal IDs:
await client.getProposal(contractId, proposalId);
Simulate a Proposal
Proposals can be simulated via the API. The results of a simulation (SimulationResponse
) will be stored and can be retrieved with the getProposalSimulation
endpoint.
const proposal = await client.getProposal(contractId, proposalId);
// import the ABI and create an ethers interface
const contractInterface = new ethers.utils.Interface(contractABI);
// encode function data
const data = contractInterface.encodeFunctionData(proposal.functionInterface.name, proposal.functionInputs);
const simulation = await client.simulateProposal(
proposal.contractId, // contractId
proposal.proposalId, // proposalId
{
transactionData: {
// this is the default hardhat address
from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // change this to impersonate the `from` address
data,
to: proposal.contract.address,
value: proposal.metadata.sendValue,
},
// default to latest finalized block,
// can be up to 100 blocks ahead of current block,
// does not support previous blocks
blockNumber: undefined,
},
);
You can also optionally set the simulate
flag as part of the createProposal
request (as long as this is not a batch proposal) to simulate the proposal within the same request. You can override simulation parameters by setting the overrideSimulationOpts
property, which is a SimulationRequest
object.
const proposalWithSimulation = await client.createProposal({
contract: {
address: '0xA91382E82fB676d4c935E601305E5253b3829dCD',
network: 'mainnet',
// provide abi OR overrideSimulationOpts.transactionData.data
abi: JSON.stringify(contractABI),
},
title: 'Flash',
description: 'Call the Flash() function',
type: 'custom',
metadata: {
sendTo: '0xA91382E82fB676d4c935E601305E5253b3829dCD',
sendValue: '10000000000000000',
sendCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
type: 'native',
},
},
functionInterface: { name: 'flash', inputs: [] },
functionInputs: [],
via: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
viaType: 'EOA',
// set simulate to true
simulate: true,
// optional
overrideSimulationOpts: {
transactionData: {
// or instead of ABI, you can provide data
data: '0xd336c82d',
},
},
});
Enabling the simulate flag as part of the createProposal request is not currently supported for batch proposals.
|
A simulation may fail due to a number of reasons, such as network congestion, unstable providers or hitting a quota limitation. We would advise you to track the response code to assure a successful response was returned. If a transaction was reverted with a reason string, this can be obtained from the response object under response.meta.returnString . A transaction revert can be tracked from response.meta.reverted .
|
Contracts Endpoint
The /contracts
endpoint can be used to manage the contracts imported into the Defender Admin dashboard. By issuing a PUT
to the endpoint you can create or update a contract given its network and address:
DATA='{
"address": "0x179810822f56b0e79469189741a3fa5f2f9a7631",
"network": "rinkeby",
"name": "My Contract",
"abi": "..."
}'
curl \
-X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d "$DATA" \
"https://defender-api.openzeppelin.com/admin/contracts"
You can also issue a GET
request to the same endpoint to retrieve a list of all contracts imported.
Verifications Endpoint
The /verifications
endpoint can be used to verify the deployed bytecode of any contract in any of the networks supported by Defender. By issuing a POST
to the endpoint you issue a verification request whose results will be stored in Defender’s address book. That in turn will make those results available to anyone with access to your Defender workspace.
DATA='
{
artifactUri: 'https://gist.githubusercontent.com/johndoe/506bff068172583d4d82ef53ba01e26c/raw/5f142de4c33aefddb2625b2cce1e6aba7791ebdc/compile-artifact.json',
solidityFilePath: 'contracts/Vault.sol',
contractName: 'VaultV2',
contractAddress: '0x38e373CC414e90dDec45cf7166d497409902e998',
contractNetwork: 'rinkeby',
referenceUri: 'https://ci-run-or-git-commit.example/',
}'
curl \
-X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d "$DATA" \
"https://defender-api.openzeppelin.com/admin/verifications"
You can also issue a GET
request to `/verifications/${contractNetwork}/${contractAddress} to get the latest verification information associated to contractAddress in Defender. For the example above, this would be:
curl \
-X GET \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
"https://defender-api.openzeppelin.com/admin/verifications/rinkeby/0x38e373CC414e90dDec45cf7166d497409902e998"
For a more in-depth discussion of bytecode verification in Defender, refer to the Defender Admin Verification section of this documentation.