---
# System prepended metadata

title: Aca-py code breakdown

---

# Aca-py code breakdown
## DID resolution API
### Step 1: API call
Endpoint: GET `/resolver/resolve/{did}`

From `aries_cloudagent/resolver/routes.py`:
```python=
async def register(app: web.Application):
    """Register routes."""

    app.add_routes(
        [
            web.get(
                "/resolver/resolve/{did}",
                resolve_did,
                allow_head=False,
            ),
        ]
    )
```
### Step 2: `resolve_did` function
- It takes a `request` object of type `web.Request` as an argument. This object represents an HTTP request.

- It extracts the `AdminRequestContext` from the request object. This context might contain information about the current request, such as headers, user information, etc.

- It retrieves the did (Decentralized Identifier) from the request's match info. This did is part of the URL path.

- It opens a session with the current profile context. Within this session, it injects a `DIDResolver` instance. This resolver is likely responsible for handling operations related to DIDs.

- It attempts to resolve the did using the `resolve_with_metadata` method of the `DIDResolver`. This method returns a `ResolutionResult` object that contains the resolved DID document along with some metadata.

- If the did cannot be found, it raises a `web.HTTPNotFound` exception. If the DID method is not supported, it raises a `web.HTTPNotImplemented` exception. If there's a generic resolver error, it raises a `web.HTTPInternalServerError` exception. In each case, it includes the reason for the error.

- If the did is successfully resolved, it serializes the `ResolutionResult` object into JSON format and returns it as a response to the HTTP request.

From `aries_cloudagent/resolver/routes.py`:
```python=
async def resolve_did(request: web.Request):
    """Retrieve a did document."""
    context: AdminRequestContext = request["context"]

    did = request.match_info["did"]
    try:
        async with context.profile.session() as session:
            resolver = session.inject(DIDResolver)
        result: ResolutionResult = await resolver.resolve_with_metadata(
            context.profile, did
        )
    except DIDNotFound as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    except DIDMethodNotSupported as err:
        raise web.HTTPNotImplemented(reason=err.roll_up) from err
    except ResolverError as err:
        raise web.HTTPInternalServerError(reason=err.roll_up) from err
    return web.json_response(result.serialize())
```
### Step 3: `resolve_did_with_metadata`
- It starts by recording the current time as `resolution_start_time`.
- It then calls the `_resolve` method, which retrieves the resolver and document associated with the provided DID.
- It calculates the duration it took to resolve the DID by subtracting `resolution_start_time` from the current time.
- It creates a `ResolutionMetadata` object, which includes the type of the resolver, the name of the resolver class, the time the DID was retrieved, and the duration it took to resolve the DID.
- Finally, it returns a `ResolutionResult` object, which includes the resolved document and the `ResolutionMetadata`.

From `aries_cloudagent/resolver/did_resolver.py`
```python=
async def resolve_with_metadata(
    self, profile: Profile, did: Union[str, DID], *, timeout: Optional[int] = None
) -> ResolutionResult:
    """Resolve a DID and return the ResolutionResult."""
    resolution_start_time = datetime.now(tz=timezone.utc)

    resolver, doc = await self._resolve(profile, did, timeout=timeout)

    time_now = datetime.now(tz=timezone.utc)
    duration = int((time_now - resolution_start_time).total_seconds() * 1000)
    retrieved_time = time_now.strftime("%Y-%m-%dT%H:%M:%SZ")
    resolver_metadata = ResolutionMetadata(
        resolver.type, type(resolver).__qualname__, retrieved_time, duration
    )
    return ResolutionResult(doc, resolver_metadata)
```

### Step 4: `_resolve` method
- It first checks if the did argument is an instance of the `DID` class. If it is, it converts it to a string. If it's not, it validates the did string.
- It then iterates over the resolvers that match the did and profile using the `_match_did_to_resolver` method.
- For each resolver, it attempts to resolve the did within a certain timeout period (either the provided timeout or the `DEFAULT_TIMEOUT` if no timeout is specified). If the did is successfully resolved, it returns the resolver and the resolved document.
- If the did cannot be resolved by any of the resolvers (i.e., a `DIDNotFound` exception is raised), it logs a debug message and continues to the next resolver.
- If none of the resolvers can resolve the did, it raises a `DIDNotFound` exception.

From `aries_cloudagent/resolver/did_resolver.py`
```python=
async def _resolve(
    self,
    profile: Profile,
    did: Union[str, DID],
    service_accept: Optional[Sequence[Text]] = None,
    *,
    timeout: Optional[int] = None,
) -> Tuple[BaseDIDResolver, dict]:
    """Retrieve doc and return with resolver.

    This private method enables the public resolve and resolve_with_metadata
    methods to share the same logic.
    """
    if isinstance(did, DID):
        did = str(did)
    else:
        DID.validate(did)
    for resolver in await self._match_did_to_resolver(profile, did):
        try:
            LOGGER.debug("Resolving DID %s with %s", did, resolver)
            document = await asyncio.wait_for(
                resolver.resolve(
                    profile,
                    did,
                    service_accept,
                ),
                timeout if timeout is not None else self.DEFAULT_TIMEOUT,
            )
            LOGGER.debug("Resolved DID %s with %s: %s", did, resolver, document)
            return resolver, document
        except DIDNotFound:
            LOGGER.debug("DID %s not found by resolver %s", did, resolver)

    raise DIDNotFound(f"DID {did} could not be resolved")
```

### Step 5: `_match_did_to_resolver` function
The `_match_did_to_resolver` function is part of the `DIDResolver` class in the `aries_cloudagent/resolver/did_resolver.py` file. This function is used to match a given DID (Decentralized Identifier) to the appropriate resolver based on the method of the DID.

The function takes two parameters: `profile` and `did`. The `profile` parameter is an instance of the `Profile` class, which represents the active profile for the agent. The `did` parameter is the Decentralized Identifier that needs to be resolved.

The function returns a list of resolvers that support the given DID. The resolvers are sorted based on their priority and registration order. Native resolvers (those that are part of the Aries Cloud Agent Python) are given higher priority.

This function is used internally by the `DIDResolver` class to determine which resolver to use when resolving a DID. It's important to note that this function is a private method, indicated by the underscore prefix, and is not intended to be used directly outside of the `DIDResolver` class.
```python=
async def _match_did_to_resolver(
    self, profile: Profile, did: str
) -> Sequence[BaseDIDResolver]:
    """Generate supported DID Resolvers.

    Native resolvers are yielded first, in registered order followed by
    non-native resolvers in registered order.
    """
    valid_resolvers = [
        resolver
        for resolver in self.resolvers
        if await resolver.supports(profile, did)
    ]
    LOGGER.debug("Valid resolvers for DID %s: %s", did, valid_resolvers)
    native_resolvers = filter(lambda resolver: resolver.native, valid_resolvers)
    non_native_resolvers = filter(
        lambda resolver: not resolver.native, valid_resolvers
    )
    resolvers = list(chain(native_resolvers, non_native_resolvers))
    if not resolvers:
        raise DIDMethodNotSupported(f'No resolver supporting DID "{did}" loaded')
    return resolvers
```
### Step 6: Resolve the did using the resolver
- This function loops through all the did resolvers that are supported, and tries to resolve the did using `resolver.resolve` function, which is implemented in `aries_cloudagent/resolver/base.py`. It either gets resolved or results in timeout. In the timeout case, it retries with the next resolver, till there are resolvers left. If the did cannot be resolved, it returns a `DIDNotFound` error.
- This function also validates the did, and if the did is illegal it returns error.


From `aries_cloudagent/resolver/did_resolver.py`
```python=
async def _resolve(
        self,
        profile: Profile,
        did: Union[str, DID],
        service_accept: Optional[Sequence[Text]] = None,
        *,
        timeout: Optional[int] = None,
    ) -> Tuple[BaseDIDResolver, dict]:
        """Retrieve doc and return with resolver.

        This private method enables the public resolve and resolve_with_metadata
        methods to share the same logic.
        """
        if isinstance(did, DID):
            did = str(did)
        else:
            DID.validate(did)
        for resolver in await self._match_did_to_resolver(profile, did):
            try:
                LOGGER.debug("Resolving DID %s with %s", did, resolver)
                document = await asyncio.wait_for(
                    resolver.resolve(
                        profile,
                        did,
                        service_accept,
                    ),
                    timeout if timeout is not None else self.DEFAULT_TIMEOUT,
                )
                LOGGER.debug("Resolved DID %s with %s: %s", did, resolver, document)
                return resolver, document
            except DIDNotFound:
                LOGGER.debug("DID %s not found by resolver %s", did, resolver)

        raise DIDNotFound(f"DID {did} could not be resolved")
```

### Step 7: Resolving the `did` using `BaseDIDResolver`
The `BaseDIDResolver` class is the base class inherited by all the did resolvers in aca-py. It contains the outline of all the methods required for resolving dids.
The resolve method of the BaseDIDResolver class is used to resolve a DID (Decentralized Identifier) using the specific resolver.

- It first checks if the did argument is an instance of the `DID` class. If it is, it converts it to a string. If it's not, it validates the did string.
- It then checks if the current resolver supports the method of the provided did by calling the `supports` method. If the method is not supported, it raises a `DIDMethodNotSupported` exception.
- It constructs a `cache_key` using the class name of the resolver and the did.
- It tries to get the resolved DID document from the cache. If the result is found in the cache, it returns the result immediately.
- If the result is not found in the cache, it calls the `_resolve` method to resolve the did. The `_resolve` method is supposed to be implemented by subclasses of `BaseDIDResolver`.
- It stores the result from `_resolve` in the cache with a default TTL (Time To Live), and then returns the result.
- If the cache is not available, it directly calls the `_resolve` method and returns the result.

From `aries_cloudagent/resolver/base.py`
```python=
async def resolve(
    self,
    profile: Profile,
    did: Union[str, DID],
    service_accept: Optional[Sequence[Text]] = None,
) -> dict:
    """Resolve a DID using this resolver.

    Handles caching of results.
    """
    if isinstance(did, DID):
        did = str(did)
    else:
        DID.validate(did)
    if not await self.supports(profile, did):
        raise DIDMethodNotSupported(
            f"{self.__class__.__name__} does not support DID method for: {did}"
        )

    cache_key = f"resolver::{type(self).__name__}::{did}"
    cache = profile.inject_or(BaseCache)
    if cache:
        async with cache.acquire(cache_key) as entry:
            if entry.result:
                return entry.result
            else:
                result = await self._resolve(profile, did, service_accept)
                await entry.set_result(result, ttl=self.DEFAULT_TTL)
                return result

    return await self._resolve(profile, did, service_accept)
```

The `_resolve` method at the end is implemented differently by different dids (like did:indy, did:peer, did:web, etc)

### Step 8.1: How `Indy` resolves dids
- It starts by getting an instance of the `BaseMultitenantManager` from the profile. If the `BaseMultitenantManager` is available, it means the system is configured for multi-tenancy. In this case, it creates an instance of `IndyLedgerRequestsExecutor` with the profile. If not, it simply gets the `IndyLedgerRequestsExecutor` from the profile.
```python
multitenant_mgr = profile.inject_or(BaseMultitenantManager)
if multitenant_mgr:
    ledger_exec_inst = IndyLedgerRequestsExecutor(profile)
else:
    ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor)
```
- It then tries to get the ledger for the given DID. If no ledger instance is configured, it raises a `NoIndyLedger` exception.
```python
ledger = (
    await ledger_exec_inst.get_ledger_for_identifier(
        did,
        txn_record_type=GET_KEY_FOR_DID,
    )
)[1]
if not ledger:
    raise NoIndyLedger("No Indy ledger instance is configured.")
```
- It then tries to get the recipient key and all endpoints for the DID from the ledger. If it encounters a `LedgerError`, it raises a `DIDNotFound` exception.
```python
try:
    async with ledger:
        recipient_key = await ledger.get_key_for_did(did)
        endpoints: Optional[dict] = await ledger.get_all_endpoints_for_did(did)
except LedgerError as err:
    raise DIDNotFound(f"DID {did} could not be resolved") from err
```

- It then creates a `DIDDocumentBuilder` with the DID.
```python
builder = DIDDocumentBuilder(DID(did))
```

- It adds a verification method to the builder using the recipient key, and references this verification method in the authentication and assertion methods of the builder.
```python
vmethod = builder.verification_method.add(
    Ed25519VerificationKey2018, ident="key-1", public_key_base58=recipient_key
)
builder.authentication.reference(vmethod.id)
builder.assertion_method.reference(vmethod.id)
```

- It then adds services to the builder using the `add_services` method.
```python
self.add_services(builder, endpoints, vmethod, service_accept)
```

- Finally, it builds the DID document and returns it serialized.
```python
result = builder.build()
return result.serialize()
```

It is interesting to see that the did is not stored as a `diddoc` in Indy ledger, instead it is built at the runtime.

From `aries_cloudagent/resolver/default/indy.py`
```python=
async def _resolve(
    self,
    profile: Profile,
    did: str,
    service_accept: Optional[Sequence[Text]] = None,
) -> dict:
    """Resolve an indy DID."""
    multitenant_mgr = profile.inject_or(BaseMultitenantManager)
    if multitenant_mgr:
        ledger_exec_inst = IndyLedgerRequestsExecutor(profile)
    else:
        ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor)
    ledger = (
        await ledger_exec_inst.get_ledger_for_identifier(
            did,
            txn_record_type=GET_KEY_FOR_DID,
        )
    )[1]
    if not ledger:
        raise NoIndyLedger("No Indy ledger instance is configured.")

    try:
        async with ledger:
            recipient_key = await ledger.get_key_for_did(did)
            endpoints: Optional[dict] = await ledger.get_all_endpoints_for_did(did)
    except LedgerError as err:
        raise DIDNotFound(f"DID {did} could not be resolved") from err

    builder = DIDDocumentBuilder(DID(did))

    vmethod = builder.verification_method.add(
        Ed25519VerificationKey2018, ident="key-1", public_key_base58=recipient_key
    )
    builder.authentication.reference(vmethod.id)
    builder.assertion_method.reference(vmethod.id)
    self.add_services(builder, endpoints, vmethod, service_accept)

    result = builder.build()
    return result.serialize()
```

### Step 8.2 how `did:web` gets resolved
The `_resolve` method of the WebDIDResolver class is responsible for resolving did:web DIDs (Decentralized Identifiers).

Here's a step-by-step explanation of what the method does:

- It takes three parameters: `profile`, `did`, and `service_accept`. The `did` parameter is the DID to be resolved.

- It transforms the `did` into a URL using the `__transform_to_url` method. It works according to https://w3c-ccg.github.io/did-method-web/#read-resolve

- It creates an HTTP session and sends a GET request to the URL.

- If the response status is 200 (OK), it tries to parse the response text into a `DIDDocument` using the `from_json` method from the `pydid` library. If the parsing is successful, it serializes the `DIDDocument` into a dictionary and returns it.

- If the response status is 404 (Not Found), it raises a `DIDNotFound` exception.

- If the response status is anything else, it raises a `ResolverError` exception.

From `aries_cloudagent/resolver/default/web.py`
```python=
async def _resolve(
    self,
    profile: Profile,
    did: str,
    service_accept: Optional[Sequence[Text]] = None,
) -> dict:
    """Resolve did:web DIDs."""

    url = self.__transform_to_url(did)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            if response.status == 200:
                try:
                    # Validate DIDDoc with pyDID
                    did_doc = DIDDocument.from_json(await response.text())
                    return did_doc.serialize()
                except Exception as err:
                    raise ResolverError(
                        "Response was incorrectly formatted"
                    ) from err
            if response.status == 404:
                raise DIDNotFound(f"No document found for {did}")
            raise ResolverError(
                "Could not find doc for {}: {}".format(did, await response.text())
            )

```

## Out of Band invitation (`POST /out-of-band/create-invitation`)
The POST /out-of-band/create-invitation API endpoint is implemented in the `invitation_create` function in the `aries_cloudagent/protocols/out_of_band/v1_0/routes.py` file.

Here's a brief overview of how it works:

- The function first retrieves the request context and the request body. If the request body exists, it is parsed as JSON.
```python
context: AdminRequestContext = request["context"]
body = await request.json() if request.body_exists else {}
```
- It then retrieves various parameters from the request body, such as `attachments`, `handshake_protocols`, `use_public_did`, `use_did`, `use_did_method`, `metadata`, `my_label`, `alias`, `mediation_id`, `protocol_version`, `goal_code`, `goal`, and others.
```python
attachments = body.get("attachments")
handshake_protocols = body.get("handshake_protocols", [])
service_accept = body.get("accept")
use_public_did = body.get("use_public_did", False)
use_did = body.get("use_did")
use_did_method = body.get("use_did_method")
metadata = body.get("metadata")
my_label = body.get("my_label")
alias = body.get("alias")
mediation_id = body.get("mediation_id")
protocol_version = body.get("protocol_version")
goal_code = body.get("goal_code")
goal = body.get("goal")
multi_use = json.loads(request.query.get("multi_use", "false"))
auto_accept = json.loads(request.query.get("auto_accept", "null"))
create_unique_did = json.loads(request.query.get("create_unique_did", "false"))
```
- It then creates an instance of `OutOfBandManager` and calls its `create_invitation` method with the retrieved parameters to create a new invitation.
```python
oob_mgr = OutOfBandManager(profile)
try:
    invi_rec = await oob_mgr.create_invitation(
        my_label=my_label,
        auto_accept=auto_accept,
        public=use_public_did,
        use_did=use_did,
        use_did_method=use_did_method,
        hs_protos=[
            h for h in [HSProto.get(hsp) for hsp in handshake_protocols] if h
        ],
        multi_use=multi_use,
        create_unique_did=create_unique_did,
        attachments=attachments,
        metadata=metadata,
        alias=alias,
```
- If the `create_invitation` method call is successful, the function returns a JSON response with the serialized invitation record. If an error occurs during the process, it raises an HTTP 400 Bad Request error with the reason for the error.
```python
return web.json_response(invi_rec.serialize())
```
- This endpoint is used to create a new connection invitation. The parameters it accepts allow for customization of the invitation, such as whether to use a public DID, the handshake protocols to use, whether the invitation is for multi-use, and more.

## Out of Band invitation (`POST /out-of-band/receive-invitation`)
The `POST /out-of-band/receive-invitation` API endpoint is implemented in the `invitation_receive` function in the file `aries_cloudagent/protocols/out_of_band/v1_0/routes.py`.

Here's how it works:

- The function takes an `aiohttp` request object as an argument.
- It retrieves the `AdminRequestContext` from the request context.
- It checks if the configuration allows receipt of invitations. If not, it raises an `HTTPForbidden` error.
- It creates an instance of `OutOfBandManager` with the profile from the context.
- It retrieves the body of the request and several query parameters.
- It attempts to deserialize the body into an `InvitationMessage` and pass it to the `receive_invitation` method of the `OutOfBandManager` along with the other parameters.
- If there are any errors during this process, it raises an HTTPBadRequest error.
- If successful, it returns a JSON response with the serialized result of the `receive_invitation` method.
Here's the relevant code:

```python=
async def invitation_receive(request: web.BaseRequest):
    context: AdminRequestContext = request["context"]
    if context.settings.get("admin.no_receive_invites"):
        raise web.HTTPForbidden(
            reason="Configuration does not allow receipt of invitations"
        )

    profile = context.profile
    oob_mgr = OutOfBandManager(profile)

    body = await request.json()
    auto_accept = json.loads(request.query.get("auto_accept", "null"))
    alias = request.query.get("alias")
    use_existing_conn = json.loads(request.query.get("use_existing_connection", "true"))
    mediation_id = request.query.get("mediation_id")
    try:
        invitation = InvitationMessage.deserialize(body)
        result = await oob_mgr.receive_invitation(
            invitation,
            auto_accept=auto_accept,
            alias=alias,
            use_existing_connection=use_existing_conn,
            mediation_id=mediation_id,
        )
    except (DIDXManagerError, StorageError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    return web.json_response(result.serialize())
```

This function is part of the Out-of-Band protocol, which is used to establish connections between agents. The `POST /out-of-band/receive-invitation` endpoint is used to receive an invitation from another agent.

## DID-Exchange Invitation (`POST /didexchange/create-request`)
### Step 1: function `didx_create_request_implicit`
The `didx_create_request_implicit` function is an asynchronous function that handles the creation and sending of a request to an implicit invitation. Here's a step-by-step explanation of how it works:

- The function takes an `aiohttp` request object as an argument.

- It extracts various parameters from the request query, including `their_public_did`, `my_label`, `my_endpoint`, `mediation_id`, `alias`, `use_public_did`, `use_did`, `use_did_method`, `goal_code`, `goal`, `auto_accept`, and `protocol`.

- It retrieves the profile from the request context and creates an instance of `DIDXManager` with the profile.

- It then calls the `create_request_implicit` method of the `DIDXManager` instance, passing all the extracted parameters. This method is responsible for creating the DID exchange request.

- If the `create_request_implicit` method raises a `StorageNotFoundError`, the function returns a `HTTPNotFound` response with the error message.

- If the `create_request_implicit` method raises a `StorageError`, `WalletError`, `DIDXManagerError`, or `BaseModelError`, the function returns a `HTTPBadRequest` response with the error message.

- If the `create_request_implicit` method executes successfully, the function returns a `json_response` with the serialized DID exchange request.

From `aries_cloudagent/protocols/didexchange/v1_0/routes.py`
```python=
async def didx_create_request_implicit(request: web.BaseRequest):
    """Request handler for creating and sending a request to an implicit invitation.

    Args:
        request: aiohttp request object

    Returns:
        The resulting connection record details

    """
    context: AdminRequestContext = request["context"]

    their_public_did = request.query["their_public_did"]
    my_label = request.query.get("my_label") or None
    my_endpoint = request.query.get("my_endpoint") or None
    mediation_id = request.query.get("mediation_id") or None
    alias = request.query.get("alias") or None
    use_public_did = json.loads(request.query.get("use_public_did", "null"))
    use_did = request.query.get("use_did") or None
    use_did_method = request.query.get("use_did_method") or None
    goal_code = request.query.get("goal_code") or None
    goal = request.query.get("goal") or None
    auto_accept = json.loads(request.query.get("auto_accept", "null"))
    protocol = request.query.get("protocol") or None

    profile = context.profile
    didx_mgr = DIDXManager(profile)
    try:
        didx_request = await didx_mgr.create_request_implicit(
            their_public_did=their_public_did,
            my_label=my_label,
            my_endpoint=my_endpoint,
            mediation_id=mediation_id,
            use_public_did=use_public_did,
            use_did=use_did,
            use_did_method=use_did_method,
            alias=alias,
            goal_code=goal_code,
            goal=goal,
            auto_accept=auto_accept,
            protocol=protocol,
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    except (StorageError, WalletError, DIDXManagerError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    return web.json_response(didx_request.serialize())
```
The `DIDXManager` class is used for managing connections under RFC 23 (DID exchange). It's part of the `aries_cloudagent/protocols/didexchange/v1_0/manager.py` file. This class is responsible for handling the creation, retrieval, and management of DID exchange connections. It inherits from the `BaseConnectionManager` class.

### Step 2: function `create_request_implicit`
This function creates a DID exchange request for implicit invitation.
- It first retrives the public did(if public did is enabled in request), otherwise it takes the did provided by user.
- Then it retrieves the connection record by using `my_did` and `their_did`. If connection already exists it throws error.
- It sets the parameters and creates a connection record by instantiating a  `ConnRecord` instance.
- Then it creates the invitation request by calling `create_request` for the other party and then returns the connection record.

From `aries_cloudagent/protocols/didexchange/v1_0/manager.py`
```python=
async def create_request_implicit(
    self,
    their_public_did: str,
    my_label: Optional[str] = None,
    my_endpoint: Optional[str] = None,
    mediation_id: Optional[str] = None,
    use_public_did: bool = False,
    alias: Optional[str] = None,
    goal_code: Optional[str] = None,
    goal: Optional[str] = None,
    auto_accept: bool = False,
    protocol: Optional[str] = None,
    use_did: Optional[str] = None,
    use_did_method: Optional[str] = None,
) -> ConnRecord:
    """Create and send a request against a public DID only (no explicit invitation).

    Args:
        their_public_did: public DID to which to request a connection
        my_label: my label for request
        my_endpoint: my endpoint
        mediation_id: record id for mediation with routing_keys, service endpoint
        use_public_did: use my public DID for this connection
        goal_code: Optional self-attested code for sharing intent of connection
        goal: Optional self-attested string for sharing intent of connection
        auto_accept: auto-accept a corresponding connection request

    Returns:
        The new `ConnRecord` instance

    """

    if use_did and use_did_method:
        raise DIDXManagerError("Cannot specify both use_did and use_did_method")

    if use_public_did and use_did:
        raise DIDXManagerError("Cannot specify both use_public_did and use_did")

    if use_public_did and use_did_method:
        raise DIDXManagerError(
            "Cannot specify both use_public_did and use_did_method"
        )

    my_info = None
    async with self.profile.session() as session:
        wallet = session.inject(BaseWallet)
        if use_public_did:
            my_info = await wallet.get_public_did()
            if not my_info:
                raise WalletError("No public DID configured")
            if (
                my_info.did == their_public_did
                or f"did:sov:{my_info.did}" == their_public_did
            ):
                raise DIDXManagerError(
                    "Cannot connect to yourself through public DID"
                )
        elif use_did:
            my_info = await wallet.get_local_did(use_did)

        if my_info:
            try:
                await ConnRecord.retrieve_by_did(
                    session,
                    their_did=their_public_did,
                    my_did=my_info.did,
                )
                raise DIDXManagerError(
                    "Connection already exists for their_did "
                    f"{their_public_did} and my_did {my_info.did}"
                )
            except StorageNotFoundError:
                pass

    auto_accept = bool(
        auto_accept
        or (
            auto_accept is None
            and self.profile.settings.get("debug.auto_accept_requests")
        )
    )
    protocol = protocol or DIDEX_1_0
    conn_rec = ConnRecord(
        my_did=(
            my_info.did if my_info else None
        ),  # create-request will fill in on local DID creation
        their_did=their_public_did,
        their_label=None,
        their_role=ConnRecord.Role.RESPONDER.rfc23,
        invitation_key=None,
        invitation_msg_id=None,
        alias=alias,
        their_public_did=their_public_did,
        connection_protocol=protocol,
        accept=ConnRecord.ACCEPT_AUTO if auto_accept else ConnRecord.ACCEPT_MANUAL,
    )
    request = await self.create_request(  # saves and updates conn_rec
        conn_rec=conn_rec,
        my_label=my_label,
        my_endpoint=my_endpoint,
        mediation_id=mediation_id,
        goal_code=goal_code,
        goal=goal,
        use_did_method=use_did_method,
    )
    conn_rec.request_id = request._id
    conn_rec.state = ConnRecord.State.REQUEST.rfc160
    async with self.profile.session() as session:
        await conn_rec.save(session, reason="Created connection request")
    responder = self.profile.inject_or(BaseResponder)
    if responder:
        await responder.send(request, connection_id=conn_rec.connection_id)

    return conn_rec
```

### Step 3: function `create_request`
- This function validates the input, creates parameters for registering a request.
- If `their_public_did` is present in connection request, it tries to resolve the did by calling `resolve_didcomm_services` function, which internally calls the previously discussed `resolver.resolve` function. Then it checks the services. 

From `aries_cloudagent/protocols/didexchange/v1_0/manager.py`
```python=
async def create_request(
        self,
        conn_rec: ConnRecord,
        my_label: Optional[str] = None,
        my_endpoint: Optional[str] = None,
        mediation_id: Optional[str] = None,
        goal_code: Optional[str] = None,
        goal: Optional[str] = None,
        use_did_method: Optional[str] = None,
    ) -> DIDXRequest:
        """Create a new connection request for a previously-received invitation.

        Args:
            conn_rec: The `ConnRecord` representing the invitation to accept
            my_label: My label for request
            my_endpoint: My endpoint
            mediation_id: The record id for mediation that contains routing_keys and
                service endpoint
            goal_code: Optional self-attested code for sharing intent of connection
            goal: Optional self-attested string for sharing intent of connection
        Returns:
            A new `DIDXRequest` message to send to the other agent

        """
        if use_did_method and use_did_method not in self.SUPPORTED_USE_DID_METHODS:
            raise DIDXManagerError(
                f"Unsupported use_did_method: {use_did_method}. Supported methods: "
                f"{self.SUPPORTED_USE_DID_METHODS}"
            )

        # Mediation Support
        mediation_records = await self._route_manager.mediation_records_for_connection(
            self.profile,
            conn_rec,
            mediation_id,
            or_default=True,
        )

        if my_endpoint:
            my_endpoints = [my_endpoint]
        else:
            my_endpoints = []
            default_endpoint = self.profile.settings.get("default_endpoint")
            if default_endpoint:
                my_endpoints.append(default_endpoint)
            my_endpoints.extend(self.profile.settings.get("additional_endpoints", []))

        if not my_label:
            my_label = self.profile.settings.get("default_label")
            assert my_label

        did_url = None
        if conn_rec.their_public_did is not None:
            services = await self.resolve_didcomm_services(conn_rec.their_public_did)
            if services:
                did_url = services[0].id

        pthid = conn_rec.invitation_msg_id or did_url

        if conn_rec.connection_protocol == DIDEX_1_0:
            did, attach = await self._legacy_did_with_attached_doc(
                conn_rec, my_endpoints, mediation_records
            )
        else:
            if conn_rec.accept == ConnRecord.ACCEPT_AUTO or use_did_method is None:
                # If we're auto accepting or engaging in 1.1 without setting a
                # use_did_method, default to did:peer:4
                use_did_method = "did:peer:4"
            try:
                did, attach = await self._qualified_did_with_fallback(
                    conn_rec,
                    my_endpoints,
                    mediation_records,
                    use_did_method,
                )
            except LegacyHandlingFallback:
                did, attach = await self._legacy_did_with_attached_doc(
                    conn_rec, my_endpoints, mediation_records
                )

        request = DIDXRequest(
            label=my_label,
            did=did,
            did_doc_attach=attach,
            goal=goal,
            goal_code=goal_code,
        )

        if conn_rec.connection_protocol == DIDEX_1_0:
            request.assign_version("1.0")

        request.assign_thread_id(thid=request._id, pthid=pthid)

        # Update connection state
        conn_rec.request_id = request._id
        conn_rec.state = ConnRecord.State.REQUEST.rfc160
        async with self.profile.session() as session:
            await conn_rec.save(session, reason="Created connection request")

        # Idempotent; if routing has already been set up, no action taken
        await self._route_manager.route_connection_as_invitee(
            self.profile, conn_rec, mediation_records
        )

        return request
```

## DID Exchange receive invitation (`POST /didexchange/receive-request`)
The `POST /didexchange/receive-request` API endpoint is used to accept a stored connection request.
### Step 1: function `didx_receive_request_implicit`
- It extracts the requred data from request body, and then instantiates a `DIDXManager` instance
- It retrieves the `didx_request` from the `didx_mgr` by passing on the body.
- It recieves the request by calling `didx_mgr.receive_request` function. Then it returns the result.

From `aries_cloudagent/protocols/didexchange/v1_0/routes.py`
```python=
async def didx_receive_request_implicit(request: web.BaseRequest):
    """Request handler for receiving a request against public DID's implicit invitation.

    Args:
        request: aiohttp request object

    Returns:
        The resulting connection record details

    """
    context: AdminRequestContext = request["context"]

    body = await request.json()
    alias = request.query.get("alias")
    auto_accept = json.loads(request.query.get("auto_accept", "null"))

    profile = context.profile
    didx_mgr = DIDXManager(profile)
    try:
        didx_request = DIDXRequest.deserialize(body)
        conn_rec = await didx_mgr.receive_request(
            request=didx_request,
            recipient_did=didx_request._thread.pthid.split(":")[-1],
            alias=alias,
            auto_accept_implicit=auto_accept,
        )
        result = conn_rec.serialize()
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    except (StorageError, WalletError, DIDXManagerError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    return web.json_response(result)
```

### Step 2: Function `receive_request`
- If the function recieves a recipient `verkey`, it receives a DID Exchange request against a pairwise (not public) DID (`_receive_request_pairwise_did`). Otherwise it recieves against the public did (`_receive_request_public_did`).

### Step 3.1: Function `_receive_request_pairwise_did`
- Here it first tries to get the connection record, otherwise create one if it does not exists.
- Then it extracts details from body like `alias`, `their_did`, `request_id`, etc.
- If did doc is given, it matches the request did with the did doc.
- If did doc is not present, it resolves the did (using `resolve_didcomm_services` present in `aries_cloudagent/connections/base_manager.py`, it internally uses the resolve method of normal dids).

### Step 3.2: Function `_receive_request_public_did`
- It first checks if public invites are enabled
- Then it gets the public did and connection record.
- Then it extracts the did doc matches it with the did like previous. If it is not present, it resolves the did.

## Issue Credential (`POST /issue-credential-2.0/send-offer`)
The `POST /issue-credential-2.0/send-offer` endpoint is implemented in the `aries_cloudagent/protocols/issue_credential/v2_0/routes.py` file. This endpoint is used to send a credential offer to a holder.

### Step 1: function `credential_exchange_send_free_offer`
- The endpoint handler function receives a web request object as an argument. This request object contains the necessary data to create and send a credential offer.

- The handler function extracts the necessary data from the request object. This data includes the connection ID, filter specification, auto-issue flag, auto-remove flag, replacement ID, credential preview, and comment.

- The handler function then calls the `_create_free_offer` function, passing in the extracted data. This function creates a credential offer and a related exchange record.

- The handler function then saves the credential exchange record and sends the credential offer message to the holder.

- Finally, the handler function returns a JSON response containing the result of the operation.

from `aries_cloudagent/protocols/issue_credential/v2_0/routes.py`
```python=
async def credential_exchange_send_free_offer(request: web.BaseRequest):
    """Request handler for sending free credential offer.

    An issuer initiates a such a credential offer, free from any
    holder-initiated corresponding credential proposal with preview.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context: AdminRequestContext = request["context"]
    profile = context.profile
    outbound_handler = request["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    filt_spec = body.get("filter")
    if not filt_spec:
        raise web.HTTPBadRequest(reason="Missing filter")
    auto_issue = body.get(
        "auto_issue", context.settings.get("debug.auto_respond_credential_request")
    )
    auto_remove = body.get(
        "auto_remove", not profile.settings.get("preserve_exchange_records")
    )
    replacement_id = body.get("replacement_id")
    comment = body.get("comment")
    preview_spec = body.get("credential_preview")
    trace_msg = body.get("trace")

    cred_ex_record = None
    conn_record = None
    try:
        async with profile.session() as session:
            conn_record = await ConnRecord.retrieve_by_id(session, connection_id)
        if not conn_record.is_ready:
            raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

        cred_ex_record, cred_offer_message = await _create_free_offer(
            profile=profile,
            filt_spec=filt_spec,
            connection_id=connection_id,
            auto_issue=auto_issue,
            auto_remove=auto_remove,
            preview_spec=preview_spec,
            comment=comment,
            trace_msg=trace_msg,
            replacement_id=replacement_id,
        )
        result = cred_ex_record.serialize()

    except (
        BaseModelError,
        AnonCredsIssuerError,
        IndyIssuerError,
        LedgerError,
        StorageNotFoundError,
        V20CredFormatError,
        V20CredManagerError,
    ) as err:
        LOGGER.exception("Error preparing free credential offer")
        if cred_ex_record:
            async with profile.session() as session:
                await cred_ex_record.save_error_state(session, reason=err.roll_up)
        # other party cannot yet receive a problem report about our failed protocol start
        raise web.HTTPBadRequest(reason=err.roll_up)

    await outbound_handler(cred_offer_message, connection_id=connection_id)

    trace_event(
        context.settings,
        cred_offer_message,
        outcome="credential_exchange_send_free_offer.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
```

### Step 2: function `_create_free_offer`
- The `_create_free_offer` function first creates a `V20CredProposal` object using the provided filter specification and credential preview. It then creates a `V20CredExRecord` object using the connection ID, auto-issue flag, auto-remove flag, and the serialized credential proposal.

- The `_create_free_offer` function then creates a `V20CredManager` object and calls its `create_offer` method, passing in the `V20CredExRecord` object, comment, and replacement ID. This method returns a credential exchange record and a credential offer message.

From `aries_cloudagent/protocols/issue_credential/v2_0/routes.py`
```python=
async def _create_free_offer(
    profile: Profile,
    filt_spec: Mapping = None,
    connection_id: str = None,
    auto_issue: bool = False,
    auto_remove: bool = False,
    replacement_id: str = None,
    preview_spec: dict = None,
    comment: str = None,
    trace_msg: bool = None,
):
    """Create a credential offer and related exchange record."""

    cred_preview = V20CredPreview.deserialize(preview_spec) if preview_spec else None
    cred_proposal = V20CredProposal(
        comment=comment,
        credential_preview=cred_preview,
        **_formats_filters(filt_spec),
    )
    cred_proposal.assign_trace_decorator(
        profile.settings,
        trace_msg,
    )

    cred_ex_record = V20CredExRecord(
        connection_id=connection_id,
        initiator=V20CredExRecord.INITIATOR_SELF,
        role=V20CredExRecord.ROLE_ISSUER,
        cred_proposal=cred_proposal.serialize(),
        auto_issue=auto_issue,
        auto_remove=auto_remove,
        trace=trace_msg,
    )

    cred_manager = V20CredManager(profile)
    (cred_ex_record, cred_offer_message) = await cred_manager.create_offer(
        cred_ex_record,
        comment=comment,
        replacement_id=replacement_id,
    )

    return (cred_ex_record, cred_offer_message)
```

## Send Credential Request (`POST /issue-credential-2.0/records/{cred_ex_id}/send-request`)

### Step 1: function `credential_exchange_send_free_request`
From `aries_cloudagent/protocols/issue_credential/v2_0/routes.py`
```python=
async def credential_exchange_send_free_request(request: web.BaseRequest):
    """Request handler for sending free credential request.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context: AdminRequestContext = request["context"]
    profile = context.profile
    outbound_handler = request["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    comment = body.get("comment")
    filt_spec = body.get("filter")
    if not filt_spec:
        raise web.HTTPBadRequest(reason="Missing filter")
    auto_remove = body.get(
        "auto_remove", not profile.settings.get("preserve_exchange_records")
    )
    trace_msg = body.get("trace")
    holder_did = body.get("holder_did")

    conn_record = None
    cred_ex_record = None
    try:
        try:
            async with profile.session() as session:
                conn_record = await ConnRecord.retrieve_by_id(session, connection_id)
            if not conn_record.is_ready:
                raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")
        except StorageNotFoundError as err:
            raise web.HTTPBadRequest(reason=err.roll_up) from err

        cred_manager = V20CredManager(profile)

        cred_proposal = V20CredProposal(
            comment=comment,
            **_formats_filters(filt_spec),
        )

        cred_ex_record = V20CredExRecord(
            connection_id=connection_id,
            auto_remove=auto_remove,
            cred_proposal=cred_proposal.serialize(),
            initiator=V20CredExRecord.INITIATOR_SELF,
            role=V20CredExRecord.ROLE_HOLDER,
            trace=trace_msg,
        )

        cred_ex_record, cred_request_message = await cred_manager.create_request(
            cred_ex_record=cred_ex_record,
            holder_did=holder_did,
            comment=comment,
        )

        result = cred_ex_record.serialize()

    except (
        BaseModelError,
        AnonCredsHolderError,
        IndyHolderError,
        LedgerError,
        StorageError,
        V20CredManagerError,
    ) as err:
        LOGGER.exception("Error preparing free credential request")
        if cred_ex_record:
            async with profile.session() as session:
                await cred_ex_record.save_error_state(session, reason=err.roll_up)
        # other party cannot yet receive a problem report about our failed protocol start
        raise web.HTTPBadRequest(reason=err.roll_up)

    await outbound_handler(cred_request_message, connection_id=connection_id)

    trace_event(
        context.settings,
        cred_request_message,
        outcome="credential_exchange_send_free_request.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
```

### Step 2: function `cred_manager.create_request`
From `aries_cloudagent/protocols/issue_credential/v2_0/manager.py`
```python=
async def create_request(
    self, cred_ex_record: V20CredExRecord, holder_did: str, comment: str = None
) -> Tuple[V20CredExRecord, V20CredRequest]:
    """Create a credential request.

    Args:
        cred_ex_record: credential exchange record for which to create request
        holder_did: holder DID
        comment: optional human-readable comment to set in request message

    Returns:
        A tuple (credential exchange record, credential request message)

    """
    if cred_ex_record.cred_request:
        raise V20CredManagerError(
            "create_request() called multiple times for "
            f"v2.0 credential exchange {cred_ex_record.cred_ex_id}"
        )

    # react to credential offer, use offer formats
    if cred_ex_record.state:
        if cred_ex_record.state != V20CredExRecord.STATE_OFFER_RECEIVED:
            raise V20CredManagerError(
                f"Credential exchange {cred_ex_record.cred_ex_id} "
                f"in {cred_ex_record.state} state "
                f"(must be {V20CredExRecord.STATE_OFFER_RECEIVED})"
            )

        cred_offer = cred_ex_record.cred_offer

        input_formats = cred_offer.formats
    # start with request (not allowed for indy -> checked in indy format handler)
    # use proposal formats
    else:
        cred_proposal = cred_ex_record.cred_proposal
        input_formats = cred_proposal.formats

    request_formats = []
    # Format specific create_request handler
    for format in input_formats:
        cred_format = V20CredFormat.Format.get(format.format)

        if cred_format:
            request_formats.append(
                await cred_format.handler(self.profile).create_request(
                    cred_ex_record, {"holder_did": holder_did}
                )
            )

    if len(request_formats) == 0:
        raise V20CredManagerError(
            "Unable to create credential request. No supported formats"
        )

    cred_request_message = V20CredRequest(
        comment=comment,
        formats=[format for (format, _) in request_formats],
        requests_attach=[attach for (_, attach) in request_formats],
    )

    # Assign thid (and optionally pthid) to message
    cred_request_message.assign_thread_from(cred_ex_record.cred_offer)
    cred_request_message.assign_trace_decorator(
        self._profile.settings, cred_ex_record.trace
    )

    cred_ex_record.thread_id = cred_request_message._thread_id
    cred_ex_record.state = V20CredExRecord.STATE_REQUEST_SENT
    cred_ex_record.cred_request = cred_request_message

    async with self._profile.session() as session:
        await cred_ex_record.save(session, reason="create v2.0 credential request")

    return (cred_ex_record, cred_request_message)
```
If `cred_format` is given it creates the credential according to that format. Formats include Indy, LD proof etc.

### Step 3.1 Indy credential
#### Function `create_request`
From `aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py`
```python=
async def create_request(
    self, cred_ex_record: V20CredExRecord, request_data: Mapping = None
) -> CredFormatAttachment:
    """Create indy credential request."""
    # Temporary shim while the new anoncreds library integration is in progress
    if self.anoncreds_handler:
        return await self.anoncreds_handler.create_request(
            cred_ex_record,
            request_data,
        )

    if cred_ex_record.state != V20CredExRecord.STATE_OFFER_RECEIVED:
        raise V20CredFormatError(
            "Indy issue credential format cannot start from credential request"
        )

    await self._check_uniqueness(cred_ex_record.cred_ex_id)

    holder_did = request_data.get("holder_did") if request_data else None
    cred_offer = cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format)

    if "nonce" not in cred_offer:
        raise V20CredFormatError("Missing nonce in credential offer")

    nonce = cred_offer["nonce"]
    cred_def_id = cred_offer["cred_def_id"]

    async def _create():
        multitenant_mgr = self.profile.inject_or(BaseMultitenantManager)
        if multitenant_mgr:
            ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile)
        else:
            ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor)
        ledger = (
            await ledger_exec_inst.get_ledger_for_identifier(
                cred_def_id,
                txn_record_type=GET_CRED_DEF,
            )
        )[1]
        async with ledger:
            cred_def = await ledger.get_credential_definition(cred_def_id)

        holder = self.profile.inject(IndyHolder)
        request_json, metadata_json = await holder.create_credential_request(
            cred_offer, cred_def, holder_did
        )

        return {
            "request": json.loads(request_json),
            "metadata": json.loads(metadata_json),
        }

    cache_key = f"credential_request::{cred_def_id}::{holder_did}::{nonce}"
    cred_req_result = None
    cache = self.profile.inject_or(BaseCache)
    if cache:
        async with cache.acquire(cache_key) as entry:
            if entry.result:
                cred_req_result = entry.result
            else:
                cred_req_result = await _create()
                await entry.set_result(cred_req_result, 3600)
    if not cred_req_result:
        cred_req_result = await _create()

    detail_record = V20CredExRecordIndy(
        cred_ex_id=cred_ex_record.cred_ex_id,
        cred_request_metadata=cred_req_result["metadata"],
    )

    async with self.profile.session() as session:
        await detail_record.save(session, reason="create v2.0 credential request")

    return self.get_format_data(CRED_20_REQUEST, cred_req_result["request"])
```

#### Function `holder.create_credential_request`
This is an abstract method and is defined for issuer holder verifier seperately.
From `aries_cloudagent/indy/credx/holder.py`
```python=
async def create_credential_request(
    self, credential_offer: dict, credential_definition: dict, holder_did: str
) -> Tuple[str, str]:
    """Create a credential request for the given credential offer.

    Args:
        credential_offer: The credential offer to create request for
        credential_definition: The credential definition to create an offer for
        holder_did: the DID of the agent making the request

    Returns:
        A tuple of the credential request and credential request metadata

    """
    try:
        secret = await self.get_link_secret()
        (
            cred_req,
            cred_req_metadata,
        ) = await asyncio.get_event_loop().run_in_executor(
            None,
            CredentialRequest.create,
            holder_did,
            credential_definition,
            secret,
            IndyCredxHolder.LINK_SECRET_ID,
            credential_offer,
        )
    except CredxError as err:
        raise IndyHolderError("Error creating credential request") from err
    cred_req_json, cred_req_metadata_json = (
        cred_req.to_json(),
        cred_req_metadata.to_json(),
    )

    LOGGER.debug(
        "Created credential request. "
        "credential_request_json=%s credential_request_metadata_json=%s",
        cred_req_json,
        cred_req_metadata_json,
    )

    return cred_req_json, cred_req_metadata_json
```
Note: This call stack does not use did functionalities.

## Credential offer (POST `/issue-credential-2.0/records/{cred_ex_id}/send-offer`)
### `credential_exchange_send_bound_offer`
At `aries_cloudagent/protocols/issue_credential    /v2_0/routes.py`
### `create_offer`
At `aries_cloudagent/protocols/issue_credential/v2_0/manager.py`
### `Create offer` as per cred handler (indy/JSON-LD)
#### For LD-Proof:
- Function `create_offer` at `aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py`
- `assert_can_issue_with_id_and_proof_type` at `aries_cloudagent/vc/vc_ld/manager.py`
- `_did_info_for_did` at `aries_cloudagent/vc/vc_ld/manager.py`
- For askar wallet, `get_local_did` at `aries_cloudagent/wallet/askar.py`

## Issue Credential (`POST /issue-credential-2.0/records/{cred_ex_id}/issue`)
### Step 1: Function `credential_exchange_issue`
### Step 2: Function `cred_manager.issue_credential`
### Step 3: Function `cred_format.handler(self.profile).issue_credential`
This function is format specific.
For indy:
### Step 4: `issue_credential` at `aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py`

## Credential issue handler `(Receive Credential callback) /issue_credential_v2_0/`
### Step 1: `handle` function
At `aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py`
### Step 2: Function `find_oob_record_for_inbound_message`
This function tries to get the OOB(connection) record for the credential issue recieved. If OOB record is not found it gets the connection record from request context. 
Function at `aries_cloudagent/core/oob_processor.py`

### Step 3: Function `store_credential`
Stores credential as per format.

### Step 4: Function `send_cred_ack`

## POST `/issue-credential-2.0/records/{cred_ex_id}/store`
### Step 1: Function `credential_exchange_store`
At `aries_cloudagent/protocols/issue_credential/v2_0/routes.py`

### Step 2: Get connection details following step 2 of prev section
### Step 3: Function `store_credential`
At `aries_cloudagent/protocols/issue_credential/v2_0/manager.py`
Store the credential as per format.

## POST `/issue-credential-2.0/records/{cred_ex_id}/send-offer`
Similiar to endpoint POST `/issue-credential-2.0/send-offer`

## POST `/issue-credential-2.0/send-proposal`
### Step 1: Function `credential_exchange_send_proposal`
At `aries_cloudagent/protocols/issue_credential/v2_0/routes.py`
It calls `ConnRecord.retrieve_by_id` to get the connection record by ID.

### Step 2: Function `create_proposal`
At `aries_cloudagent/protocols/issue_credential/v2_0/manager.py`
Creates a proposal as per the format.

## POST `/issue-credential-2.0/records/{cred_ex_id}/problem-report`
### Step 1: Function `credential_exchange_problem_report`
At`aries_cloudagent/protocols/issue_credential/v2_0/routes.py`
It extracts the `cred_ex_record` and updates it.

### Step 2: Function `problem_report_for_record`
At `aries_cloudagent/protocols/issue_credential/v2_0/__init__.py`
Creates a `V20CredProblemReport` object and returns it.


## POST `/present-proof-2.0/send-request`

### Step 1: `present_proof_send_free_request`
At `aries_cloudagent/protocols/present_proof/v2_0/routes.py`
### Step 2: Retireve Connecton record from `ConnRecord.retrieve_by_id`
At `aries_cloudagent/messaging/models/base_record.py`
### Step 3: Create request object using `V20PresRequest` constructor
At `aries_cloudagent/protocols/present_proof/v2_0/messages/pres_request.py`
### Step 4: Create a presentation exchange record by function `create_exchange_for_request`
At `aries_cloudagent/protocols/present_proof/v2_0/manager.py`
#### Step 4.1: Create a exchange record using constructor `V20PresExRecord`
At `aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py`
### Step 5: Serialize and send the message to the connection.
At `aries_cloudagent/protocols/present_proof/v2_0/routes.py`

## (Receive Request callback) `/present_proof_v2_0/`
### Step 1: Function `handle`
At `aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py`
#### Check if connection is ready
```python
# If connection is present it must be ready for use
if context.connection_record and not context.connection_ready:
    raise HandlerException("Connection used for presentation request not ready")

# Find associated oob record
oob_processor = context.inject(OobMessageProcessor)
oob_record = await oob_processor.find_oob_record_for_inbound_message(context)

# Either connection or oob context must be present
if not context.connection_record and not oob_record:
    raise HandlerException(
        "No connection or associated connectionless exchange found for"
        " presentation request"
    )
```
### Step 2: Create another proposal for the holder using `V20PresExRecord`
At `aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py`

## Send Presentation `POST /present-proof-2.0/records/{pres_ex_id}/send-presentation`
### `present_proof_send_presentation`
At `aries_cloudagent/protocols/present_proof/v2_0/routes.py`
### `create_pres`
At `aries_cloudagent/protocols/present_proof/v2_0/manager.py`
### `create_pres` as per handler (indy/LD)
#### For Indy
- `create_pres` at `aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py`
- `indy_proof_req_preview2indy_requested_creds` at `aries_cloudagent/indy/models/xform.py`
#### For LD
- `create_pres` at `aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py`

## (Receive Proof callback) `/present_proof_v2_0/`
### Get connection details: `find_oob_record_for_inbound_message`
At `aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_handler.py`
### `receive_pres`
At `aries_cloudagent/protocols/present_proof/v2_0/manager.py`

## POST `/present-proof-2.0/records/{pres_ex_id}/verify-presentation`
### `present_proof_verify_presentation`
At `aries_cloudagent/protocols/present_proof/v2_0/routes.py`

### `verify_pres`
At `aries_cloudagent/protocols/present_proof/v2_0/manager.py`

### `verify_pres` as per handler
For indy
- `verify_pres` at `aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py`

For JSON-LD
- `verify_pres` at `aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py`