1 Commits

Author SHA1 Message Date
d425b58cfe get_interchain: per_chain + chains options (default first anchor per chain); bump 0.5.0
All checks were successful
Publish to PyPI Registry / publish (release) Successful in 23s
transaction.get_interchain / block.get_interchain take per_chain= and chains=
kwargs mapping to prime-node's ?perChain=&chains= params. Default (no kwargs)
returns one anchor per chain. Shared interchain_query() helper, exported; pytest.
2026-06-05 10:56:33 -04:00
7 changed files with 106 additions and 8 deletions

View File

@@ -151,6 +151,24 @@ client = DragonchainSDK(
- `proof_measure.report(req)` — Per-transaction "securedBy" report over interchain anchors. - `proof_measure.report(req)` — Per-transaction "securedBy" report over interchain anchors.
- `proof_measure.health()` — Service liveness. - `proof_measure.health()` — Service liveness.
## Interchain trace
`transaction.get_interchain` / `block.get_interchain` trace a prime block to the
public-chain anchors covering it. By default they return the **first anchor per
chain** (anchor proofs are chained, so the earliest per chain is the meaningful
one):
```python
# Default: first anchor per chain (ETH, BTC, …)
trace = client.block.get_interchain("42")
# Up to 3 anchors per chain
trace = client.block.get_interchain("42", per_chain=3)
# All anchors, only the ETH-mainnet chain ("1"); "0" = BTC
trace = client.block.get_interchain("42", per_chain=0, chains=["1"])
```
## Proof Measure ## Proof Measure
`proof-measure` is a separate, **public, unauthenticated** Dragonchain service `proof-measure` is a separate, **public, unauthenticated** Dragonchain service

View File

@@ -81,6 +81,7 @@ from .models import (
SecurityResult, SecurityResult,
TransactionReport, TransactionReport,
) )
from .interchain import interchain_query
from .proof_measure import DEFAULT_BASE_URL as PROOF_MEASURE_DEFAULT_BASE_URL from .proof_measure import DEFAULT_BASE_URL as PROOF_MEASURE_DEFAULT_BASE_URL
from .proof_measure import ProofMeasureClient from .proof_measure import ProofMeasureClient
from .system import SystemClient from .system import SystemClient
@@ -88,7 +89,7 @@ from .transaction import TransactionClient
from .transaction_type import TransactionTypeClient from .transaction_type import TransactionTypeClient
from .unauthenticated_client import UnauthenticatedClient from .unauthenticated_client import UnauthenticatedClient
__version__ = "0.4.0" __version__ = "0.5.0"
class DragonchainSDK: class DragonchainSDK:
@@ -148,6 +149,7 @@ __all__ = [
"ProofMeasureClient", "ProofMeasureClient",
"UnauthenticatedClient", "UnauthenticatedClient",
"PROOF_MEASURE_DEFAULT_BASE_URL", "PROOF_MEASURE_DEFAULT_BASE_URL",
"interchain_query",
# models # models
"Block", "Block",
"BlockHeader", "BlockHeader",

View File

@@ -1,6 +1,9 @@
"""Block endpoints.""" """Block endpoints."""
from typing import List, Optional
from .client import Client from .client import Client
from .interchain import interchain_query
from .models import Block, InterchainTrace from .models import Block, InterchainTrace
@@ -11,10 +14,21 @@ class BlockClient:
def get(self, block_id: str) -> Block: def get(self, block_id: str) -> Block:
return self._client.get(f"/api/v1/block/{block_id}", Block) return self._client.get(f"/api/v1/block/{block_id}", Block)
def get_interchain(self, block_id: str) -> InterchainTrace: def get_interchain(
self,
block_id: str,
per_chain: Optional[int] = None,
chains: Optional[List[str]] = None,
) -> InterchainTrace:
"""Trace a block to the validator (verification) blocks that validated it """Trace a block to the validator (verification) blocks that validated it
and the public-chain interchain anchors those validator blocks were and the public-chain interchain anchors those validator blocks were
bundled into.""" bundled into.
By default returns the first anchor per public chain (anchor proofs are
chained, so the earliest per chain is the meaningful one). ``per_chain``
caps anchors per chain (1 = first per chain; 0 = all); ``chains``
restricts to specific chain ids."""
return self._client.get( return self._client.get(
f"/api/v1/block/{block_id}/interchain", InterchainTrace f"/api/v1/block/{block_id}/interchain{interchain_query(per_chain, chains)}",
InterchainTrace,
) )

27
prime_sdk/interchain.py Normal file
View File

@@ -0,0 +1,27 @@
"""Query builder for the interchain-trace endpoints.
Anchor proofs are chained, so by default the trace returns the first anchor per
public chain; these options change how many and which chains are returned.
"""
from typing import List, Optional
from urllib.parse import quote
def interchain_query(
per_chain: Optional[int] = None, chains: Optional[List[str]] = None
) -> str:
"""Build the "?perChain=...&chains=..." suffix for the interchain trace
endpoints.
Returns "" when nothing is set (the server then applies its defaults: one
anchor per chain, all chains). ``per_chain`` caps anchors per public chain,
earliest-first (1 = first per chain; 0 = all). ``chains`` restricts to the
given chain ids ("1" = ETH mainnet, "0" = BTC; testnet ids differ).
"""
parts: List[str] = []
if per_chain is not None:
parts.append(f"perChain={int(per_chain)}")
if chains:
parts.append("chains=" + ",".join(quote(str(c), safe="") for c in chains))
return "?" + "&".join(parts) if parts else ""

View File

@@ -1,6 +1,9 @@
"""Transaction endpoints.""" """Transaction endpoints."""
from typing import List, Optional
from .client import CONTENT_TYPE_JSON, Client from .client import CONTENT_TYPE_JSON, Client
from .interchain import interchain_query
from .models import ( from .models import (
InterchainTrace, InterchainTrace,
ListTransactionsResponse, ListTransactionsResponse,
@@ -40,13 +43,25 @@ class TransactionClient:
def get(self, transaction_id: str) -> Transaction: def get(self, transaction_id: str) -> Transaction:
return self._client.get(f"/api/v1/transaction/{transaction_id}", Transaction) return self._client.get(f"/api/v1/transaction/{transaction_id}", Transaction)
def get_interchain(self, transaction_id: str) -> InterchainTrace: def get_interchain(
self,
transaction_id: str,
per_chain: Optional[int] = None,
chains: Optional[List[str]] = None,
) -> InterchainTrace:
"""Trace a transaction to the validator (verification) blocks that """Trace a transaction to the validator (verification) blocks that
validated its prime block and the public-chain interchain anchors those validated its prime block and the public-chain interchain anchors those
validator blocks were bundled into. If the transaction is still pending validator blocks were bundled into. If the transaction is still pending
(not yet in a block) the trace's lists are empty.""" (not yet in a block) the trace's lists are empty.
By default returns the first anchor per public chain (anchor proofs are
chained, so the earliest per chain is the meaningful one). ``per_chain``
caps anchors per chain (1 = first per chain; 0 = all); ``chains``
restricts to specific chain ids."""
return self._client.get( return self._client.get(
f"/api/v1/transaction/{transaction_id}/interchain", InterchainTrace f"/api/v1/transaction/{transaction_id}/interchain"
f"{interchain_query(per_chain, chains)}",
InterchainTrace,
) )
def list(self) -> ListTransactionsResponse: def list(self) -> ListTransactionsResponse:

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "prime-sdk-python" name = "prime-sdk-python"
version = "0.4.0" version = "0.5.0"
description = "A self-contained Python SDK for interacting with Dragonchain nodes." description = "A self-contained Python SDK for interacting with Dragonchain nodes."
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.9"

22
tests/test_interchain.py Normal file
View File

@@ -0,0 +1,22 @@
"""Offline tests for the interchain-trace query builder."""
from prime_sdk import interchain_query
def test_empty_when_nothing_set():
assert interchain_query() == ""
assert interchain_query(None, None) == ""
assert interchain_query(chains=[]) == ""
def test_per_chain_including_zero():
assert interchain_query(per_chain=1) == "?perChain=1"
assert interchain_query(per_chain=0) == "?perChain=0"
def test_chains_joined_with_commas():
assert interchain_query(chains=["1", "0"]) == "?chains=1,0"
def test_combined():
assert interchain_query(per_chain=2, chains=["1", "0"]) == "?perChain=2&chains=1,0"