# Request-response oracle

{% hint style="info" %}
This guide provides step-by-step instructions for creating data requests to the NCFX API using the request-response oracle system on the Canton blockchain. The guide assumes that the RequestFactory contract already exists and focuses on the request creation process using HTTP requests.
{% endhint %}

{% hint style="success" icon="copy" %}
DAR files must be uploaded on your node to use this Oracle [\[Click here to download\]](https://kaiko-delivery-links.s3.us-east-1.amazonaws.com/request-response-oracle-ncfx.zip)
{% endhint %}

## Overview

The request-response oracle enables secure requests for NCFX financial data through the Canton blockchain. The request creation process involves:<br>

1. **Preparing request parameters:** Gather NCFX data request
2. **Retrieving required contracts:** Get Amulet rules and open mining round contracts
3. **Contract exercise:** Submit the transaction to create a data request on the blockchain

<p align="center"></p>

<p align="center"><img src="https://1969223162-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8j8Uy5gOsywos3YYkZNv%2Fuploads%2FcPRgRZQ1Ht4tBMRWqm3D%2Funknown.png?alt=media&#x26;token=d87490cb-9c1f-4033-a7d4-12c9d8eb7713" alt="" data-size="original"></p>

## Prerequisites

#### Required Information

You need the following information before starting:<br>

* **Canton participant endpoint:** URL of your Canton participant JSON API
* **Canton authentication token:** Valid token for API access
* **Consumer party ID:** Your party identifier in Canton (e.g., Consumer::1220...)
* **RequestFactory contract ID:** Contract ID for the request factory
* **DSO Party ID:** DSO party identifier
* **NCFX arguments:**
  * Data request (stringified JSON)
* **Amulet contract ID:** An Amulet contract for payment
* **Payment locked amount:** Amount to lock for the request

### Environment Setup

Ensure you have:

* HTTP client capability (curl, fetch, requests library, etc.)
* JSON parsing capability
* Access to the Canton blockchain network
* A canton node connected to the same domain as the oracle's node
* Uploaded the Request-Response Oracle DAR file to your canton node
* Sufficient Amulet balance for the request

## Creating a Data Request

### Prepare NCFX Request Parameters

Prepare your NCFX data request as a stringified JSON. Example:

```json
{
    "dataset": "live/midrate/v1/rates",
    "currencypair": "GBPUSD",
    "tenor": "SPT",
    "deliverability": "D",
    "type": "O"
}
```

**Stringify the request (remove newlines and escape quotes):**

{% code overflow="wrap" %}

```
"{\n\"dataset\": \"live/midrate/v1/rates\",\n\"currencypair\": \"GBPUSD\",\n\"tenor\": \"SPT\",\n\"deliverability\": \"D\",\n\"type\": \"O\"\n}"
```

{% endcode %}

**Required Parameters:**

* `dataRequest` → Stringified NCFX data request
* `lockedAmount` → Payment amount to lock
* `dso` → The DSO party
* `amuletRulesCid` → The official amulet rules current contract ID
* `openRoundCid` → The current OpenMiningRound contract ID
* `inputs` → A list of amulets YOUR\_CONSUMER\_PARTY owns

### Submit Request Creation Transaction

Make a POST request to Canton to exercise the CreateRequest choice:

**Request:**

{% code overflow="wrap" %}

```
POST {PARTICIPANT_NODE_JSON_API_URL}/v2/commands/submit-and-wait-for-transaction-tree
Authorization: Bearer {CANTON_TOKEN}
Content-Type: application/json

```

{% endcode %}

**Request body:**

{% code overflow="wrap" %}

```json
{
    "actAs": [
        "{YOUR_CONSUMER_PARTY}"
    ],
    "commandId": "create-request-1",
    "commands": [
        {
            "actAs": [
                "{YOUR_CONSUMER_PARTY}"
            ],
            "commandId": "create-request-1",
            "ExerciseCommand": {
                "contractId": "{REQUEST_FACTORY_CONTRACT_ID}",
                "templateId": "3778b498a5b2703be4ab088451b13d3688a81e421c4332cd5e4a0cfa4bd245f1:Request-ResponseOracle.RequestFactory:RequestFactory",
                "choice": "CreateRequest",
                "choiceArgument": {
                    "dso": "{DSO_PARTY}",
                    "amuletRulesCid": "{AMULET_RULES_CONTRACT_ID}",
                    "openRoundCid": "{OPEN_ROUND_CONTRACT_ID}",
                    "inputs": [
                        {
                            "tag": "InputAmulet",
                            "value": "{YOUR_AMULET_CONTRACT_ID}"
                        }
                    ],
                    "dataRequest": "{STRINGIFIED_NCFX_DATA_REQUEST}",
                    "lockedAmount": {PAYMENT_LOCKED_AMOUNT}
                }
            }
        }
    ],
    "disclosedContracts": [
        {
            "templateId": "{AMULET_PACKAGE_ID}:Splice.AmuletRules:AmuletRules",
            "contractId": "{AMULET_RULES_CONTRACT_ID}",
            "synchronizerId": "{GLOBAL_DOMAIN_SYNCHRONIZER_ID}",
            "createdEventBlob": "{AMULET_RULES_DISCLOSED_BLOB}"
        },
        {
            "templateId": "{AMULET_PACKAGE_ID}:Splice.Round:OpenMiningRound",
            "contractId": "{OPEN_ROUND_CONTRACT_ID}",
            "synchronizerId": "{GLOBAL_DOMAIN_SYNCHRONIZER_ID}",
            "createdEventBlob": "{OPEN_ROUND_DISCLOSED_BLOB}"
        }
    ]
}


```

{% endcode %}

The exerciseResult part in the response looks like:

{% code overflow="wrap" %}

```json
{
    "transferResult": {
        "round": {
            "number": "29450"
        },
        "summary": {
            "inputAppRewardAmount": "0.0000000000",
            "inputValidatorRewardAmount": "0.0000000000",
            "inputSvRewardAmount": "0.0000000000",
            "inputAmuletAmount": "267332873.0096101736",
            "balanceChanges": [
                [
                    "kaiko-devnet-1::12200fd571073e933e107afec72902c1a59f4f0ec3be8ef4ada5231c9d3f6d93de2c",
                    {
                        "changeToInitialAmountAsOfRoundZero": "3.4415328900",
                        "changeToHoldingFeesRate": "0.0001168602"
                    }
                ]
            ],
            "holdingFees": "0.0000000000",
            "outputFees": [
                "0.0000000000"
            ],
            "senderChangeFee": "0.0000000000",
            "senderChangeAmount": "267332868.0096101736",
            "amuletPrice": "0.1628090000",
            "inputValidatorFaucetAmount": "0.0000000000",
            "inputUnclaimedActivityRecordAmount": "0.0000000000"
        },
        "createdAmulets": [
            {
                "tag": "TransferResultLockedAmulet",
                "value": "00ed9e1b4151e1ef755c6299e13daaaf9a4db647e276d531450458afa8379e5a62ca121220ae6ee4724b56f56318093df7cb1eee5e6d8caa35d3ad37ce0b914b02eb86ddff"
            }
        ],
        "senderChangeAmulet": "00dfd2b1bcc153d42ee241a2ac6c524bcc2c5ae5ee0894b918b3bb302b6fc9a173ca1212204132fe27f6d8f1c0531d0ad0bdf80a36828fcf3598baceef12d4b9696da7ebc8",
        "meta": {
            "values": {
                "splice.lfdecentralizedtrust.org/sender": "kaiko-devnet-1::12200fd571073e933e107afec72902c1a59f4f0ec3be8ef4ada5231c9d3f6d93de2c",
                "splice.lfdecentralizedtrust.org/tx-kind": "transfer"
            }
        }
    },
    "requestCid": "00ac8fef95efed0583d531ac51ca1eeb2881d1c31f87b31e3ff321eb5441d619e3ca1212206c3d1b48abb01cb7ce28af486debeb9739ec6e8668fec4b19af6255e610fccbb",
    "oracleResponse": "Request created for: {\n\"dataset\": \"live/midrate/v1/rates\",\n\"currencypair\": \"GBPUSD\",\n\"tenor\": \"SPT\",\n\"deliverability\": \"D\",\n\"type\": \"O\"\n} (escrowed 5.0 Amulet until 2026-02-04T15:05:03.617409Z)",
    "lockedAmount": "5.0000000000",
    "requestId": "babc4f56d892"
}
```

{% endcode %}

## Troubleshooting

A request can fail. In this case, the request contract is archived, and a FailedRequest is created. Here are the failing scenarios:

### **1. The locked amount does not cover the service amount**

* **Cause**: The lockedAmount you specified is less than the service fee charged by the oracle
* **Solution:**
  * Increase the `lockedAmount` parameter in your request
  * Ensure you have sufficient Amulet balance

### **2. Stringified JSON request Data Is Not Valid**

* **Cause:** The `dataRequest` parameter is not a properly stringified JSON string
* **Solution:**
  * Verify your JSON is valid before stringifying
  * Ensure all quotes are properly escaped (\\")
  * Check that newlines are removed or escaped
  * Test your JSON with a validator before stringifying

### **3. NCFX Returns an Error When Sending the Request**

* **Cause:** NCFX's API returned an error when processing your request
* **Solution:**
  * Check NCFX API status and availability
  * Verify your data request format matches NCFX's API requirements
  * Ensure requested fields and instruments are available in your catalog
  * Check NCFX rate limits haven't been exceeded
  * Review NCFX's API documentation for valid request formats

## Reading the FilledRequest

### Request processing

The oracle

* detects your new request
* proceeds to the payment by unlocking your amulet, taking the service amount needed from it and sending back to the consumer party the remaining amount
* reads the Request contract
* makes the request to NCFX on your behalf
* archives the Data Request contract and writes the response data into a FilledRequest which fields look like:

{% code overflow="wrap" %}

```json
{
    "oracle": "{ORACLE_PARTY}",
    "consumer": "{CONSUMER_PARTY}",
    "requestData": "{stringified json request data}",
    "responseData": "{stringified json response data}",
    "serviceAmountCharged": "1.0000000000"
}
```

{% endcode %}

### Read the `FilledRequest` contract

Once the oracle has processed your request and received a response from NCFX, a FilledRequest contract is created. To retrieve it, you need to query the active contracts on the Canton ledger.

**Query for FilledRequest contracts**

Make a POST request to retrieve your FilledRequest contracts.

**Request:**

{% code overflow="wrap" %}

```
POST {PARTICIPANT_NODE_JSON_API_URL}/v2/state/active-contracts
Authorization: Bearer {CANTON_TOKEN}
Content-Type: application/json
```

{% endcode %}

**Request body:**

{% code overflow="wrap" %}

```json
{
    "activeAtOffset": {offset},
    "verbose": false,
    "eventFormat": {
        "filtersByParty": {
            "{YOUR_CONSUMER_PARTY}": {
                "cumulative": [
                    {
                        "identifierFilter": {
                            "TemplateFilter": {
                                "value": {
                                    "includeCreatedEventBlob": false,
                                    "templateId": "3778b498a5b2703be4ab088451b13d3688a81e421c4332cd5e4a0cfa4bd245f1:Request-ResponseOracle.Request:FilledRequest"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "verbose": false
    }
}
```

{% endcode %}

**Expected response:**

{% code overflow="wrap" %}

```json
[
    {
        "workflowId": "",
        "contractEntry": {
            "JsActiveContract": {
                "createdEvent": {
                    "offset": 627962,
                    "nodeId": 1,
                    "contractId": "{FILLED_REQUEST_CONTRACT_ID}",
                    "templateId": "3778b498a5b2703be4ab088451b13d3688a81e421c4332cd5e4a0cfa4bd245f1:Request-ResponseOracle.Request:FilledRequest",
                    "contractKey": null,
                    "createArgument": {
                        "oracle": "{ORACLE_PARTY}",
                        "consumer": "{YOUR_CONSUMER_PARTY}",
                        "requestId": "babc4f56d892",
                        "requestData": "{stringified json request data}",
                        "responseData": "{stringified json response data}",
                        "serviceAmountCharged": "1.0000000000"
                    },
                    "createdEventBlob": "",
                    "interfaceViews": [],
                    "witnessParties": [
                        "{ORACLE_PARTY}"
                    ],
                    "signatories": [
                        "{ORACLE_PARTY}"
                    ],
                    "observers": [
                        "{YOUR_CONSUMER_PARTY}"
                    ],
                    "createdAt": "2026-01-23T14:56:45.708037Z",
                    "packageName": "Request-Response-oracle",
                    "representativePackageId": "3778b498a5b2703be4ab088451b13d3688a81e421c4332cd5e4a0cfa4bd245f1",
                    "acsDelta": true
                },
                "synchronizerId": "global-domain::1220...",
                "reassignmentCounter": 0
            }
        }
    }
]
```

{% endcode %}

## **Understanding the response**

The response contains the FilledRequest contract with the following important fields in createArgument:

**Contract fields:**

<table data-header-hidden><thead><tr><th width="373.71875"></th><th></th></tr></thead><tbody><tr><td><code>oracle</code></td><td>The oracle party that processed the request</td></tr><tr><td><code>consumer</code></td><td>Your consumer party ID</td></tr><tr><td><code>requestId</code></td><td>NCFX request ID</td></tr><tr><td><code>requestData</code></td><td>The original request data you submitted (stringified JSON)</td></tr><tr><td><code>responseData</code></td><td>NCFX's response data (stringified JSON)</td></tr><tr><td><code>serviceAmountCharged</code></td><td>The actual service fee charged by the oracle</td></tr></tbody></table>

**Service amount:** The `serviceAmountCharged` shows the actual fee deducted from your locked amount. Any remaining amount from your locked amount is returned to you.
