# Canton pull oracle

{% hint style="info" %}
This guide provides step-by-step instructions for publishing BTC-USD price data to the Canton blockchain using the pull oracle system. The guide assumes that the MasterOracle and FeedEntitlement contracts already exist and focuses on the publication process using generic 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/pull-oracle-0.3.2.zip)
{% endhint %}

## Overview

The pull oracle system enables secure publication of real-time cryptocurrency market data from Kaiko API to the Canton blockchain. The publication process involves:<br>

1. Fetching market data: Retrieve signed price data from Kaiko API
2. Contract exercise: Submit transaction to publish data on blockchain

<figure><img src="https://1969223162-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8j8Uy5gOsywos3YYkZNv%2Fuploads%2FGimMqplhfvkM0PpGTvFY%2Funknown.png?alt=media&#x26;token=2a071775-2510-4aa1-b21e-43e0d392f18a" alt=""><figcaption></figcaption></figure>

The pull oracle workflow has two phases: publish and verify. In the publish phase, the client fetches a Kaiko-signed BTC‑USD observation (value, timestamp, signature) from the Kaiko Market API and submits it to Canton by exercising `PublishSignedData` on the on-chain `MasterOracle` contract. The ledger then validates the signature against the oracle key and enforces the caller’s `FeedEntitlement`; only if both checks pass is a new `PublishedSignedData` state created on-chain. In the verify phase, any consumer can read the on-chain `PublishedSignedData` (e.g., via `ExtractDataFields`) to retrieve the stored fields and rely on the fact that they were accepted only after on-chain verification.<br>

## Prerequisites

### Required information

You need the following information before starting:

* Canton participant endpoint: URL of your Canton participant
* Canton authentication token: Valid JWT token for API access
* Kaiko API key: Valid API key for accessing Kaiko market data
* Actor Party ID: Your party identifier in Canton (e.g., `PO1-DataProvider::1220...`)
* FeedEntitlement Contract ID: Contract ID for your data feed entitlement

#### Environment setup

Set up your environment configuration by copying scripts/.env.sample to scripts/.env and filling the required values

Ensure you have:

* HTTP client capability (curl, fetch, requests library, etc.)
* JSON parsing capability
* Hex encoding/decoding capability

#### Upload the dar file to your ledger

```shellscript
cd scripts

# upload
./upload_dar.sh

```

### Publishing BTC-USD price data

#### Step 1: Fetch market data from Kaiko API

Make a GET request to retrieve the latest BTC-USD price data with cryptographic signature.<br>

The price source can be chosen between:

* Kaiko Fair Market Value price (<https://us.market-api.kaiko.io/v2/data/canton/oracle/price/btc-usd>)
* Benchmark Reference Rate (<https://us.market-api.kaiko.io/v2/data/canton/oracle/brr/btc-usd>).

**Request:**

```
GET https://us.market-api.kaiko.io/v2/data/canton/oracle/price/btc-usd
X-Api-Key: your-kaiko-api-key
Content-Type: application/json

```

**Expected Response:**

```json
{
  "data": {
    "name": "btc-usd",
    "value": 67850123456,
    "decimal": 6,
    "timestamp": 1729677600000,
    "signature": "{signature}"
  },
  "canton_oracle": {
    "template_id": "{PACKAGE_ID}:PullOracle.MasterOracle:MasterOracle",
    "contract_id": "{contract_id}",
    "synchronizer_id": "{sync_id}",
    "created_event_blob": "{base64_encoded_blob}"
  }
}

```

**Extract Required Fields:**

* `data.name →` feedId (e.g., "btc-usd")
* `data.value` → price value (e.g., 67850123456)
* `data.decimal` → decimal places (e.g., 6)
* `data.timestamp` → Unix timestamp in milliseconds
* `data.signature` → Cryptographic signature from Kaiko
* canton\_oracle.\* → Disclosed contract information

#### Step 2: Submit Publication Transaction

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

**Request:**

```
POST PARTICIPANT_JSON_API_URL/v2/commands/submit-and-wait
Authorization: Bearer your-canton-token
Content-Type: application/json

```

**Request body:**

```json
{
  "actAs": [
    "{your_party_id}"
  ],
  "commandId": "{random_command_id}",
  "commands": [
    {
      "actAs": [
        "{your_party_id}"
      ],
      "commandId": "{random_command_id}",
      "ExerciseCommand": {
        "contractId": "{contract_id}",
        "templateId": "{PACKAGE_ID}:PullOracle.MasterOracle:MasterOracle", 
        "choice": "PublishSignedData",
        "choiceArgument": {
          "actor": "{your_party_id}",
          "value": 67850123456,
          "timestamp": 1729677600000,
          "signature": "{signature}",
          "entitlement": "{your_entitlement_contract_id}"
        }
      }
    }
  ],
  "disclosedContracts": [
    {
      "templateId": "{PACKAGE_ID}:PullOracle.MasterOracle:MasterOracle",
      "contractId": "{contract_id}",
      "synchronizerId": "{sync_id}",
      "createdEventBlob": "{base64_encoded_blob}"
    }
  ]
}

```

## Troubleshooting

### Common error responses

#### **Signature validation failed**

* Error: "`Signature validation failed`"
* Cause: Invalid signature or hash mismatch
* Solution:
  * Ensure signature is DER-encoded secp256k1
  * Verify hash calculation matches exactly
  * Check that signature was generated with correct private key

#### **Feed not authorized**

* Error: "`Feed not authorized`"
* Cause: FeedEntitlement doesn't include "btc-usd" feed
* Solution: Verify your entitlement contract authorizes the btc-usd feed

#### **Contract not visible**

* Error: "`Contract not visible`"
* Cause: Actor party cannot see the contracts
* Solution:
  * Verify party IDs are correct
  * Ensure contracts exist and are active
  * Check party has proper access permissions

### Debugging steps

#### Verify API response:

```
GET https://us.market-api.kaiko.io/v2/data/canton/oracle/price/btc-usd
X-Api-Key: your-api-key
```

#### Test canton connection

```
GET PARTICIPANT_JSON_API_URL/v2/health
Authorization: Bearer your-token
```

#### Check contract status

```python
POST PARTICIPANT_JSON_API_URL/v2/contracts/search
Authorization: Bearer your-token
Content-Type: application/json

{
  "templateIds": ["PullOracle.MasterOracle:MasterOracle"]
}

```

## Advanced examples

### Complete implementation example (Python)

```
import requests
from datetime import datetime

def publish_btc_price():
    # Configuration
    KAIKO_API_KEY = "your-kaiko-api-key"
    KAIKO_URL = "https://us.market-api.kaiko.io/v2/data/canton/oracle/price/btc-usd"
    CANTON_ENDPOINT = "PARTICIPANT_JSON_API_URL"
    CANTON_TOKEN = "your-canton-token"
    ACTOR_PARTY = "{your_party_id}"
    ENTITLEMENT_ID = "00f6b5eff282ced2d6b136058b15302192b912df2fdab80a98b04b49d0ab7e2fb4ca111220af7eec3e2d2a72b6255dd082adaea2ca2f56cdd5c4f15bd0b652874bce2085ce"

    # Step 1: Fetch market data
    headers = {
        "X-Api-Key": KAIKO_API_KEY,
        "Content-Type": "application/json"
    }
    
    response = requests.get(KAIKO_URL, headers=headers)
    kaiko_data = response.json()
    
    # Extract data
    value = kaiko_data["data"]["value"]
    timestamp = kaiko_data["data"]["timestamp"]
    signature = kaiko_data["data"]["signature"]
    
    # Step 2: Submit transaction
    command_id = f"publish-btc-{int(datetime.now().timestamp())}"
    
    canton_payload = {
        "actAs": [ACTOR_PARTY],
        "commandId": command_id,
        "disclosedContracts": [
            {
                "templateId": kaiko_data["canton_oracle"]["template_id"],
                "contractId": kaiko_data["canton_oracle"]["contract_id"],
                "synchronizerId": kaiko_data["canton_oracle"]["synchronizer_id"],
                "createdEventBlob": kaiko_data["canton_oracle"]["created_event_blob"]
            }
        ],
        "commands": [
            {
                "actAs": [ACTOR_PARTY],
                "commandId": command_id,
                "ExerciseCommand": {
                    "contractId": kaiko_data["canton_oracle"]["contract_id"],
                    "templateId": kaiko_data["canton_oracle"]["template_id"],
                    "choice": "PublishSignedData",
                    "choiceArgument": {
                        "actor": ACTOR_PARTY,
                        "value": value,
                        "timestamp": timestamp,
                        "signature": signature,
                        "entitlement": ENTITLEMENT_ID
                    }
                }
            }
        ]
    }
    
    canton_headers = {
        "Authorization": f"Bearer {CANTON_TOKEN}",
        "Content-Type": "application/json"
    }
    
    canton_response = requests.post(
        f"{CANTON_ENDPOINT}/v2/commands/submit-and-wait",
        json=canton_payload,
        headers=canton_headers
    )
    
    if canton_response.status_code == 200:
        result = canton_response.json()
        print("✅ Price published successfully!")
        print(f"📊 Value: {value}")
        print(f"⏰ Timestamp: {timestamp}")
        return result
    else:
        print(f"❌ Failed: {canton_response.text}")
        return None

# Execute
if __name__ == "__main__":
    publish_btc_price()
```

## Reference data

### Required contract IDs

Replace these with your actual contract IDs:

| Contract Type          | Example ID        | Description                       |
| ---------------------- | ----------------- | --------------------------------- |
| MasterOracle (BTC-USD) | 00b59a8e38e273... | Validates BTC-USD signatures      |
| FeedEntitlement        | 00f6b5eff282ce... | Authorizes your party for BTC-USD |

#### API Endpoints

| Service       | Endpoint                                                             | Purpose                 |
| ------------- | -------------------------------------------------------------------- | ----------------------- |
| Kaiko API     | <https://us.market-api.kaiko.io/v2/data/canton/oracle/price/btc-usd> | Fetch signed price data |
| Canton API    | PARTICIPANT\_JSON\_API\_URL/v2/commands/submit-and-wait              | Submit transactions     |
| Canton Health | PARTICIPANT\_JSON\_API\_URL/v2/health                                | Check API status        |

***

### PublishedSignedData on-chain reading

Here is a template example for a contract that would read the values of an on-chain PublishedSignedData instance by using the PublishedData interface of the Kaiko data standard that PublishedSignedData implements. You can find this example project in `resources/example`.

```
module PublishedSignedDataValues where

import DataStandard.DataPointV1 (PublishedData(..))
import DataStandard.Utils (Values)
import qualified DataStandard.Utils as Utils

-- | Template for extracting values from PublishedData contracts
-- This template provides utility functions to retrieve specific fields
-- from contracts implementing the PublishedData interface using the data standard Values interface
template PublishedSignedDataValues
  with
    subscriber : Party
  where
    signatory subscriber

    -- | Choice to extract specific fields from a PublishedData contract
    -- Returns feedId, value, decimal, and timestamp fields
    nonconsuming choice ExtractDataFields : (Text, Int, Int, Int)
      with
        publishedDataId : ContractId PublishedData
      controller subscriber
      do
        -- Fetch the PublishedData contract
        publishedDataView <- view <$> fetch publishedDataId
        
        -- Extract the required fields using Utils.getField from the values
        feedId <- case Utils.getField "feedId" publishedDataView.values of
          Some fid -> return fid
          None -> error "feedId field not found"
        
        value <- case Utils.getField "value" publishedDataView.values of
          Some v -> return v
          None -> error "value field not found"
        
        decimal <- case Utils.getField "decimal" publishedDataView.values of
          Some d -> return d
          None -> error "decimal field not found"
        
        timestamp <- case Utils.getField "timestamp" publishedDataView.values of
          Some t -> return t
          None -> error "timestamp field not found"
        
        return (feedId, value, decimal, timestamp)

    -- | Choice to extract all available fields from a PublishedData contract
    -- Returns the complete Values map for inspection
    nonconsuming choice ExtractAllFields : Values
      with
        publishedDataId : ContractId PublishedData
      controller subscriber
      do
        -- Fetch the PublishedData contract and get its view
        publishedDataView <- view <$> fetch publishedDataId
        
        -- Return the complete values map
        return publishedDataView.values

    -- | Choice to extract a specific field by name from a PublishedData contract

```

### Exercising `ExtractDataFields` choice

To extract the core data fields (feedId, value, decimal, timestamp) from a published data contract, use the following HTTP request:

**Request:**

```
POST PARTICIPANT_JSON_API_URL/v2/commands/submit-and-wait-for-transaction-tree
Authorization: Bearer your-canton-token
Content-Type: application/json
```

**Request body:**

```json
{
  "actAs": [
    "{subscriber_party}"
  ],
  "commandId": "command-12345",
  "commands": [
    {
      "actAs": [
        "{subscriber_party}"
      ],
      "commandId": "command-12345",
      "ExerciseCommand": {
        "contractId": "00aaa607e80925b65138e43beff8cdc821f01ce50a624bacf35b248d0d28cadaf6ca11122018d0d4db95635c2a717215bfb508c44a00ce3664b9e071309dfa7c9629ea663a",
        "templateId": "PACKAGE_ID:PublishedSignedDataValues:PublishedSignedDataValues",
        "choice": "ExtractDataFields",
        "choiceArgument": {
          "publishedDataId": "0074f05f05f1afc7916d6a88662d42845ddfe2c12347c6937c27f45191c0620466ca111220b27457a819843992a9f752abb90237c48f1185cf9938069c4316c074a23261b4"
        }
      }
    }
  ]
}

```

**Expected response**

```json
{
    "transactionTree": {
        "updateId": "1220096c8df701c40f11345c3edef0d99adb7eee50ca32a5f42e29ea34fa73ed5ca9",
        "commandId": "test-read-1235",
        "workflowId": "",
        "effectiveAt": "2025-11-21T10:18:03.758127Z",
        "offset": 88255,
        "eventsById": {
            "0": {
                "ExercisedTreeEvent": {
                    "value": {
                        "offset": 88255,
                        "nodeId": 0,
                        "contractId": "00aaf5adf31cfb40ad7a58febf9c6fafd1e748e16ea7721f5ef7fe871316e159d7ca1212204b8f72a9fc769ef8b2576a59228ad0b82063d6093ef44adc396de184283c27dd",
                        "templateId": "cf9a9eea904a0e53c05f7fa88a3e6038850fa9e68b20ce9b904e50fa080c698b:PublishedSignedDataValues:PublishedSignedDataValues",
                        "interfaceId": null,
                        "choice": "ExtractDataFields",
                        "choiceArgument": {
                            "publishedDataId": "006084902a822faa45b6dc192745b5a37eda78674ef20cc3a7c359d34f346122a6ca1212205d4e937ca7772064d24ab743ae3a50c0006341dcf859f2018ead0a772f6357fb"
                        },
                        "actingParties": [
                            "PO1-DataProvider::12200fd571073e933e107afec72902c1a59f4f0ec3be8ef4ada5231c9d3f6d93de2c"
                        ],
                        "consuming": false,
                        "witnessParties": [
                            "PO1-DataProvider::12200fd571073e933e107afec72902c1a59f4f0ec3be8ef4ada5231c9d3f6d93de2c"
                        ],
                        "lastDescendantNodeId": 1,
                        "exerciseResult": {
                            "_1": "eth-usd",
                            "_2": "286934859756",
                            "_3": "8",
                            "_4": "1763657023776"
                        },
                        "packageName": "kaiko-pull-oracle-example",
                        "implementedInterfaces": [],
                        "acsDelta": false
                    }
                }
            }
        },
        "synchronizerId": "global-domain::1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a",
        "traceContext": {
            "traceparent": "00-7eb2a8b9c4ce0358dfabfdef4d98a5d9-fda2f5120fa40f24-01",
            "tracestate": null
        },
        "recordTime": "2025-11-21T10:18:03.913291Z"
    }
}

```

#### Response fields

| `_1` | feedId (string)    | The feed identifier, e.g., "btc-usd"                             |
| ---- | ------------------ | ---------------------------------------------------------------- |
| `_2` | value (string)     | The price value as a string representation, e.g., "108542709812" |
| `_3` | decimal (string)   | Number of decimal places, e.g., "6"                              |
| `_4` | timestamp (string) | Unix timestamp in milliseconds, e.g., "1761148541270"            |

The extracted values show that the BTC-USD price was 108,542.709812 (108542709812 with 6 decimal places) at timestamp 1761148541270.
