# Manage the Address Book

API Users can propose address book changes. Address book changes are subject to the approval of the admin quorum.

## Workflow

- You create a valid JSON structure and send it to the appropriate endpoint in Fordefi’s API:
  - POST a `/v1/addressbook/contacts` request for single contact additions.
  - POST a `/v1/addressbook/contacts/batch` request for bulk add address operations.
- The API validates entries, returns submission ID or errors. You get error information only if your request has errors. In the case of a bulk add addresses request, the *entire* batch is rejected.
- The system triggers mobile approval for authorized approvers.
- After approval, the address book is updated.


## Constraints

Only users with appropriate permissions can initiate bulk addition actions:

- Traders propose addresses for Admin approval.
- For bulk add addresses requests:
  - The batch size should not surpass 1000 entries. In case you need to add more addresses, call the endpoint several times.
  - Batch processing may take up to 30 seconds for 1000 entries.
  - API rate limits apply. [Learn more](/developers/general-topics#api-rate-limits).


## Create a new contact

To create a new contact, call the [Create Contact](/api/openapi/address-book/create_contact_api_v1_addressbook_contacts_post) endpoint.

In the request, specify the following:

- `name`: The name of the contact.
- `type`: The type of the chain.
- `address`: The address on the chain.


Optionally, if you want the contact to be valid only for specific chains (within the specified chain type) or specific assets, you can specify a list of `chains` (for example, `evm_1,` `solana_mainnet`, and so on) or `assets` for which this contact should be valid.

Since address book changes are sensitive operations, they require [request signing](/developers/authentication#request-signing). For example, to add a Bitcoin address to the address book:


```shell Shell
#!bash
 
ACCESS_TOKEN='<Your API User Access Token>'
PRIVATE_KEY_FILE='<Path of your private key PEM file>'
ENDPOINT=''
BODY=''

TIMESTAMP="$(($(date +%s) * 1000))"
SIGNATURE="$(echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}" | openssl dgst -sha256 -sign ${PRIVATE_KEY_FILE} | base64 | tr -d \\n)"

echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}"

curl -v "https://api.fordefi.com${ENDPOINT}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "x-signature: ${SIGNATURE}" \
-H "x-timestamp: ${TIMESTAMP}" \
-d "${BODY}"
```


```py Python
import requests
import base64
import json
import datetime
import ecdsa
import hashlib

access_token = "<API User Access Token>"
private_key_file = "<Path of your private key PEM file>"
path = ""
request_json = 

timestamp = str(int(datetime.datetime.now(datetime.timezone.utc).timestamp()))

request_body = json.dumps(request_json)
payload = f"{path}|{timestamp}|{request_body}"

with open(private_key_file, "r") as f:
    signing_key = ecdsa.SigningKey.from_pem(f.read())

signature = signing_key.sign(
    data=payload.encode(),
    hashfunc=hashlib.sha256,
    sigencode=ecdsa.util.sigencode_der
)

resp_tx = requests.post(
    f"https://api.fordefi.com{path}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "x-signature": base64.b64encode(signature),
        "x-timestamp": timestamp.encode(),
    },
    data=request_body,
)
```


```js Javascript
const crypto = require("crypto");
const fs = require('fs');

const accessToken = "<Your API User Access Token>"
const privateKeyFile = "<Path of your private key PEM file>"
const path = ""
const requestJson = 

const gatewayHost = "api.fordefi.com"
const requestBody = JSON.stringify(requestJson)
const timestamp = new Date().getTime();
const payload = `${path}|${timestamp}|${requestBody}`;

const secretPem = fs.readFileSync(privateKeyFile, 'utf8');
const privateKey = crypto.createPrivateKey(secretPem);
const sign = crypto.createSign('SHA256').update(payload, 'utf8').end();
const signature = sign.sign(privateKey, 'base64');

fetch(`https://${gatewayHost}${path}`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        "Authorization": `Bearer ${accessToken}`,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
    },
    body: requestBody,
}).then((response) => response.text())
    .then((json) => console.log(json));
```


```rust Rust
// Using crate p256 = { version = "0.12.0", features = ["pem"] }
use base64::{engine::general_purpose, Engine as _};
use p256::ecdsa::{signature::Signer, Signature, SigningKey};
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

// This is the main function
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let access_token = "<Your API User Access Token>";
    let private_key_file = "<Path of your private key PEM file>";
    let path = "";
    let request_body = r#"
"#;

    let gateway_host = "api.fordefi.com";
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
        .to_string();
    let payload = format!("{path}|{timestamp}|{request_body}");

    let priv_pem = fs::read_to_string(private_key_file).expect("Failed to read pem file");
    let private_key = p256::SecretKey::from_sec1_pem(&priv_pem).expect("Failed to decode pem key");
    let signing_key: SigningKey = private_key.into();

    let signature: Signature = signing_key.sign(payload.as_bytes());
    let formatted_signature = general_purpose::STANDARD.encode(signature.to_der().to_bytes());
    let client = reqwest::blocking::Client::new();
    let res = client
        .post(format!("https://{gateway_host}{path}"))
        .body(request_body)
        .bearer_auth(access_token)
        .header("Content-Type", "application/json")
        .header("X-Timestamp", timestamp)
        .header("X-Signature", formatted_signature)
        .send()?
        .text()?;

    println!("Result: {res}");
    Ok(())
}
```

## Control the scope of the address

An address can be scoped to a single chain, all chains of a particular type (for example, EVM), or a specific asset. For example:

All EVM Chains

```json
{
    "name": "My EVM Contact",
    "type": "evm",
    "address": "0x3fEBb139Ba00332E0B2DE6994B0bdAA505bf318D",
    "chains": []
}
```

Single EVM Chain

```json
{
    "name": "My Ethereum Contact",
    "type": "evm",
    "address": "0x3fEBb139Ba00332E0B2DE6994B0bdAA505bf318D",
    "chains": ["evm_1"]
}
```

USDC Only

```json
{
    "name": "My USDC-only Ethereum Contact",
    "type": "evm",
    "address": "0x3fEBb139Ba00332E0B2DE6994B0bdAA505bf318D",
    "chains": ["evm_1"],
    "assets_identifiers": [
        {
            "type": "evm",
            "details": {
                "type": "erc20",
                "token": {
                    "hex_repr": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
                    "chain": "evm_1"
                }
            }
        }
    ]
}
```

## Update an existing contact

To update an existing contact, call the [Edit Contact](/api/openapi/address-book/edit_contact_api_v1_addressbook_contacts__id__proposals_post) endpoint. The edit request has the same format as the creation request, above.

## Abort a proposal

After an address book change has been proposed, and until it has been approved by the admin quorum, you can abort the proposal using the [Abort Contact](/api/openapi/address-book/abort_contact_change_proposal_api_v1_addressbook_contacts_proposals__proposal_id__abort_post) endpoint.

## List contacts

You can list all contacts in your workspace using the [List Contacts](/api/openapi/address-book/list_contacts_api_v1_addressbook_contacts_get) endpoint. This API returns contacts in all states: `pending`, `active`, and `deleted`. If you want to get only the active contacts you can use the `states` query parameter.

## Bulk add addresses using the API

You can submit a batch of address book entries through the Fordefi API.

### Request structure and endpoint

POST a request using a JSON structure that follows the format below to:
`/api/v1/addressbook/contacts/batch`

- `name`: The name of the contact.
- `type`: The type of the chain.
- `address`: The address on the chain.
- `chains`: The chains the contact belongs to. If not provided, the contact will be associated with all chains.


And optionally:

- `assets_identifiers`: The asset identifiers of the contact.
- `group_ids`: ID of the user group to retrieve.


Here is an example request:


```
{
    contacts: [
        {
            "name": "My Bitcoin Contact",
            "type": "utxo",
            "address": "1JvTPkdZPhtDR7D7qAdAJ923MzFKhmN6k4",
            "chain": "bitcoin_mainnet"
        },
        {
            "name": "My Bitcoin Contact2",
            "type": "utxo",
            "address": "tb1qsn262wn3hx37eh5cnrc0uy0kru32a8q8t9vv4k",
            "chain": "bitcoin_testnet"
        }
    ]
}
```