Skip to content

feat: data attestation actions and usage#131

Merged
MicBun merged 5 commits intomainfrom
workingAttest
Oct 22, 2025
Merged

feat: data attestation actions and usage#131
MicBun merged 5 commits intomainfrom
workingAttest

Conversation

@MicBun
Copy link
Copy Markdown
Member

@MicBun MicBun commented Oct 21, 2025

needs: trufnetwork/kwil-js#143
resolves: https://github.com/trufnetwork/truf-network/issues/1274

Summary by CodeRabbit

  • New Features

    • Added attestation APIs to request attestations, retrieve signed attestation payloads, and list attestation records; client access and type-safe interfaces exposed.
    • Added an argument-encoding utility and helpers for attestation payload handling.
  • Documentation

    • Expanded README and added a self-contained attestation example with quick-reference snippets, payload format, verification guidance, and troubleshooting.
  • Tests

    • Added integration tests covering end-to-end attestation flows, input validation, pagination, and ordering.
  • Chores

    • Updated a dependency spec and improved local test setup safety.

@MicBun MicBun requested a review from outerlook October 21, 2025 16:44
@MicBun MicBun self-assigned this Oct 21, 2025
@MicBun MicBun added the enhancement New feature or request label Oct 21, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds end-to-end attestation support: new exported AttestationAction with request/get/list methods, attestation types and validation, argument-encoding utilities, client integration and exports, examples and README docs, integration tests, and safer test-runner docker invocation.

Changes

Cohort / File(s) Summary
Core Attestation Implementation
src/contracts-api/attestationAction.ts, src/types/attestation.ts, src/util/AttestationEncoding.ts
New exported AttestationAction class with requestAttestation, getSignedAttestation, and listAttestations. New attestation types, validation utilities, encoding helpers (encodeActionArgs, readUint32LE), parsing/decoding helpers, error handling, and inline unit tests.
Client Integration
src/client/client.ts
Added loadAttestationAction() to BaseTNClient to construct and return an AttestationAction.
Public API Exports
src/index.common.ts, src/internal.ts
Exported AttestationAction and attestation-related types (RequestAttestationInput, RequestAttestationResult, GetSignedAttestationInput, SignedAttestationResult, ListAttestationsInput, AttestationMetadata).
Examples & Documentation
README.md, examples/attestation/README.md, examples/attestation/index.ts
Added README attestation section and quick reference; new example README and TypeScript script demonstrating request → poll → retrieve → list attestation flow and verification snippets.
Integration Tests & Test Setup
tests/integration/attestation.test.ts, tests/integration/trufnetwork.setup.ts
New integration tests for request/get/list flows, validation, pagination, ordering, polling. Test setup: TN_DB_IMAGE env override, docker image validation, and safer docker exec invocation.
Docs/JSDoc Tweak
src/contracts-api/action.ts
Updated JSDoc for executeWithNamedParams and executeWithActionBody (documentation only).
Package Update
package.json
Bumped @trufnetwork/kwil-js from 0.9.8 to ^0.9.9.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as Application
    participant Client as TN Client
    participant Action as AttestationAction
    participant Remote as Remote Network

    App->>Client: loadAttestationAction()
    Client->>Action: construct AttestationAction
    Action-->>App: AttestationAction instance

    rect rgba(100,150,230,0.06)
    App->>Action: requestAttestation(input)
    Action->>Action: validate + encode args
    Action->>Remote: execute(request_attestation)
    Remote-->>Action: { tx_hash }
    Action-->>App: { requestTxId }
    end

    Note over App: wait for tx confirmation (external)

    rect rgba(120,200,120,0.06)
    App->>Action: getSignedAttestation(requestTxId)
    Action->>Remote: view(get_signed_attestation)
    Remote-->>Action: Either<err, base64_payload>
    Action->>Action: decode payload -> Uint8Array
    Action-->>App: { payload }
    end

    rect rgba(230,190,100,0.05)
    App->>Action: listAttestations(filters)
    Action->>Action: validate list input
    Action->>Remote: view(list_attestations)
    Remote-->>Action: rows[]
    Action->>Action: parse rows -> AttestationMetadata[]
    Action-->>App: AttestationMetadata[]
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • outerlook
  • williamrusdyputra

Poem

🐰
I hopped through bytes and fields of green,
Encoded args and proofs unseen,
A tx was sent, a validator signed,
Now attestations stream aligned,
Crunch, nibble, verify — trust defined!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: data attestation actions and usage" directly and comprehensively describes the main changes in this pull request. The changeset introduces a new AttestationAction class with three public methods (requestAttestation, getSignedAttestation, listAttestations), along with supporting type definitions, encoding utilities, comprehensive documentation, and a complete example demonstrating how to use the attestation workflow. The title accurately captures both the core implementation (attestation actions) and the accompanying guidance (usage through examples and docs), which aligns with the primary focus of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch workingAttest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@holdex
Copy link
Copy Markdown

holdex bot commented Oct 21, 2025

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 5h Update time Oct 22, 2025, 1:21 PM
@outerlook ❌ Missing - ⚠️ Submit time -

outerlook
outerlook previously approved these changes Oct 21, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/contracts-api/action.ts (1)

690-699: Inconsistent compose metadata (value vs ref) – disable won’t find what allow stored

  • allowComposeStream stores only streamId in value.
  • getAllowedComposeStreams expects value in format "streamId:dataProvider".
  • disableComposeStream filters by wallet.toString() (StreamLocator is a POJO ⇒ "[object Object]") against $ref, not the stored value.

Result: disable call can’t locate the row; getter parsing breaks unless value actually contains both parts. Store and query a consistent composite key and use it in all three methods.

Apply this fix:

-  public async allowComposeStream(
-    stream: StreamLocator,
-    wallet: StreamLocator,
-  ): Promise<Types.GenericResponse<Types.TxReceipt>> {
-    return await this.setMetadata(
-      stream,
-      MetadataKey.AllowComposeStreamKey,
-      wallet.streamId.getId(),
-    );
-  }
+  public async allowComposeStream(
+    stream: StreamLocator,
+    child: StreamLocator,
+  ): Promise<Types.GenericResponse<Types.TxReceipt>> {
+    // Store "streamId:dataProvider" to match getAllowedComposeStreams parsing.
+    const val = `${child.streamId.getId()}:${child.dataProvider.getAddress()}`;
+    return await this.setMetadata(
+      stream,
+      MetadataKey.AllowComposeStreamKey,
+      val,
+    );
+  }
@@
-  public async disableComposeStream(
-    stream: StreamLocator,
-    wallet: StreamLocator,
-  ): Promise<Types.GenericResponse<Types.TxReceipt>> {
-    const result = await this.getMetadata(
-      stream,
-      MetadataKey.AllowComposeStreamKey,
-      wallet.toString(),
-    );
+  public async disableComposeStream(
+    stream: StreamLocator,
+    child: StreamLocator,
+  ): Promise<Types.GenericResponse<Types.TxReceipt>> {
+    // Filter by the same composite value we stored.
+    const ref = `${child.streamId.getId()}:${child.dataProvider.getAddress()}`;
+    const result = await this.getMetadata(
+      stream,
+      MetadataKey.AllowComposeStreamKey,
+      ref,
+    );

Optionally, backfill/normalize existing rows to the composite format to avoid mixed historical data. If insert_metadata populates value_ref differently, mirror the composite in ref as well during insert.

Also applies to: 704-723, 754-770

🧹 Nitpick comments (22)
tests/integration/trufnetwork.setup.ts (2)

35-35: Avoid :latest for reproducible CI

:latest drifts and can break tests. Pin to a tag or digest (e.g., ghcr.io/trufnetwork/node@sha256:...). Consider requiring TN_DB_IMAGE to be set in CI and fail fast if absent.


229-244: Network deletion check can miss matches

docker network ls --filter name=... returns partial matches and may print multiple lines; comparing stdout.trim() === NETWORK_NAME can fail. Prefer docker network inspect to check existence or split lines and look for exact equality.

-const { stdout } = await runDockerCommand(["network", "ls", "--filter", `name=${NETWORK_NAME}`, "--format", "{{.Name}}"]);
-if (stdout.trim() === NETWORK_NAME) {
+const { stdout } = await runDockerCommand(["network", "ls", "--filter", `name=^${NETWORK_NAME}$`, "--format", "{{.Name}}"]);
+if (stdout.split("\n").map(s => s.trim()).includes(NETWORK_NAME)) {
src/util/AttestationEncoding.ts (3)

22-22: Tighten types for args

args can be typed to kwil-js’s ValueType to improve safety.

-export function encodeActionArgs(args: any[]): Uint8Array {
+export function encodeActionArgs(args: Types.ValueType[]): Uint8Array {

75-80: Optional: prefer DataView for LE read/write

Manual bit ops are fine; DataView reduces edge cases and clarifies intent.

-function writeUint32LE(buffer: Uint8Array, value: number, offset: number): void {
-  buffer[offset] = value & 0xff;
-  buffer[offset + 1] = (value >> 8) & 0xff;
-  buffer[offset + 2] = (value >> 16) & 0xff;
-  buffer[offset + 3] = (value >> 24) & 0xff;
-}
+function writeUint32LE(buffer: Uint8Array, value: number, offset: number): void {
+  new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).setUint32(offset, value >>> 0, true);
+}
@@
-export function readUint32LE(buffer: Uint8Array, offset: number): number {
-  return (
-    buffer[offset] |
-    (buffer[offset + 1] << 8) |
-    (buffer[offset + 2] << 16) |
-    (buffer[offset + 3] << 24)
-  ) >>> 0; // Convert to unsigned 32-bit integer
-}
+export function readUint32LE(buffer: Uint8Array, offset: number): number {
+  return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).getUint32(offset, true);
+}

Also applies to: 89-96


98-210: Export surface: readUint32LE likely internal

If readUint32LE is exported only for tests, mark it @internal or keep it unexported and test via black-box helpers to avoid bloating the public API.

src/client/client.ts (1)

14-14: Attestation loader is consistent with other loaders

Import + loadAttestationAction look good and align with BaseTNClient patterns.

Optional: for API symmetry, consider AttestationAction.fromClient(...) similar to RoleManagement.fromClient(...).

Also applies to: 193-203

examples/attestation/README.md (3)

26-30: Avoid author-specific absolute paths

Use a project-relative path to improve portability.

-   cd /home/micbun/trufnetwork/sdk-js
+   cd path/to/sdk-js

1-267: Fix markdown hard tabs

markdownlint flags multiple hard tabs (MD010). Replace tabs with spaces in code blocks and lists to satisfy CI and keep rendering consistent.


155-195: Ethers v5 vs v6 utils API differences

The snippet uses ethers.utils.*. If the repo standardizes on ethers v6 in other places, adapt to v6 (e.g., import { keccak256, recoverAddress, hexlify } from "ethers"). Verify version and align examples accordingly.

README.md (3)

239-256: Prefer exponential backoff with jitter for polling

Fixed 2s polling can cause synchronized load and flakiness. Use capped exponential backoff with jitter and a clear max timeout. This improves resilience and reduces leader pressure.

- for (let i = 0; i < 15; i++) {
+ const started = Date.now();
+ const maxMs = 60_000; // 60s cap
+ for (let i = 0; Date.now() - started < maxMs; i++) {
    try {
      // ...
      if (signed.payload.length > 65) break; // Got signature
    } catch (e) {
-     await new Promise(resolve => setTimeout(resolve, 2000));
+     const base = Math.min(2000 * 2 ** i, 10_000);
+     const jitter = Math.floor(Math.random() * 500);
+     await new Promise(res => setTimeout(res, base + jitter));
    }
 }

261-264: Provide browser‑safe address‑to‑bytes snippet

Buffer isn’t available in browsers. Add an alternative snippet using a hex decoder for BrowserTNClient contexts.

-const myAddress = client.address().getAddress();
-const myAddressBytes = new Uint8Array(Buffer.from(myAddress.slice(2), "hex"));
+const myAddress = client.address().getAddress();
+// Node:
+const myAddressBytes =
+  typeof Buffer !== "undefined"
+    ? new Uint8Array(Buffer.from(myAddress.slice(2), "hex"))
+    // Browser:
+    : (() => {
+        const h = myAddress.slice(2);
+        const out = new Uint8Array(h.length / 2);
+        for (let i = 0; i < h.length; i += 2) out[i / 2] = parseInt(h.substr(i, 2), 16);
+        return out;
+      })();

279-291: Clarify payload spec: fixed‑length vs length‑prefixed fields

“Data provider (20 bytes, length‑prefixed)” and “Stream ID (32 bytes, length‑prefixed)” are contradictory. If these are fixed 20/32‑byte values, drop “length‑prefixed”; if they’re variable with a length prefix, avoid stating fixed sizes. Align docs with the canonical encoding used in the implementation.

examples/attestation/index.ts (2)

91-118: Replace fixed‑interval polling with capped exponential backoff

Improves success rate and reduces load under variable block times.

-const maxAttempts = 15; // 30 seconds max (2 seconds per attempt)
-for (let i = 0; i < maxAttempts; i++) {
+const started = Date.now();
+const maxMs = 60_000;
+for (let i = 0; Date.now() - started < maxMs; i++) {
   try {
     // ...
-  } catch (err) {
+  } catch (err) {
     // Not signed yet, continue polling
   }
-  // Progress indicator
-  process.stdout.write(`Attempt ${i + 1}/${maxAttempts}...`);
-  if (i < maxAttempts - 1) {
-    process.stdout.write("\r");
-    await new Promise((resolve) => setTimeout(resolve, 2000));
-  } else {
-    process.stdout.write("\n");
-  }
+  const base = Math.min(2000 * 2 ** i, 10_000);
+  const jitter = Math.floor(Math.random() * 500);
+  await new Promise((r) => setTimeout(r, base + jitter));
 }

71-78: Gate verbose logs behind an env flag

The example is very chatty; gate DEBUG output to keep it readable by default.

- console.log(`DEBUG: Full requestAttestation result:`, JSON.stringify(requestResult, null, 2));
+ if (process.env.DEBUG) console.log(`DEBUG: Full requestAttestation result:`, JSON.stringify(requestResult, null, 2));
- console.log(`DEBUG: TX confirmed at height ${txResult.height}, hash: ${txResult.tx_hash}`);
+ if (process.env.DEBUG) console.log(`DEBUG: TX confirmed at height ${txResult.height}, hash: ${txResult.tx_hash}`);
- console.log(`DEBUG: TX result - code: ${(txResult as any).tx_result?.code}, log: ${(txResult as any).tx_result?.log}`);
+ if (process.env.DEBUG) console.log(`DEBUG: TX result - code: ${(txResult as any).tx_result?.code}, log: ${(txResult as any).tx_result?.log}`);
- console.log(`DEBUG Attempt ${i + 1}: getSignedAttestation returned:`, {/* ... */});
+ if (process.env.DEBUG) console.log(`DEBUG Attempt ${i + 1}: getSignedAttestation returned:`, {/* ... */});
- console.log(`First 64 bytes (hex): ${Buffer.from(signedAttestation.payload.slice(0, 64)).toString("hex")}`);
+ if (process.env.DEBUG) console.log(`First 64 bytes (hex): ${Buffer.from(signedAttestation.payload.slice(0, 64)).toString("hex")}`);
- console.log(`    Attestation hash: ${Buffer.from(att.attestationHash).toString("hex")}`);
+ if (process.env.DEBUG) console.log(`    Attestation hash: ${Buffer.from(att.attestationHash).toString("hex")}`);

Also applies to: 90-106, 125-129, 146-153

tests/integration/attestation.test.ts (3)

84-115: Use capped exponential backoff with jitter in polling

Reduces flakiness under slow blocks or CI load; keeps total wait bounded.

-const maxAttempts = 15; // 30 seconds max
-for (let i = 0; i < maxAttempts; i++) {
+const started = Date.now();
+const maxMs = 60_000;
+for (let i = 0; Date.now() - started < maxMs; i++) {
   try {
     // ...
   } catch (e: any) {
     // Not signed yet, continue polling
     console.log(`Attempt ${i + 1}...`);
   }
-  // Wait 2 seconds before next attempt
-  await new Promise((resolve) => setTimeout(resolve, 2000));
+  const base = Math.min(2000 * 2 ** i, 10_000);
+  const jitter = Math.floor(Math.random() * 500);
+  await new Promise((resolve) => setTimeout(resolve, base + jitter));
 }

71-78: Tame test log noise

Wrap DEBUG logs behind an env flag to keep CI output lean.

- console.log(`DEBUG: Full requestAttestation result:`, JSON.stringify(requestResult, null, 2));
+ if (process.env.DEBUG) console.log(`DEBUG: Full requestAttestation result:`, JSON.stringify(requestResult, null, 2));
- console.log(`DEBUG: TX confirmed at height ${txResult.height}, hash: ${txResult.tx_hash}`);
+ if (process.env.DEBUG) console.log(`DEBUG: TX confirmed at height ${txResult.height}, hash: ${txResult.tx_hash}`);
- console.log(`DEBUG: TX result - code: ${(txResult as any).tx_result?.code}, log: ${(txResult as any).tx_result?.log}`);
+ if (process.env.DEBUG) console.log(`DEBUG: TX result - code: ${(txResult as any).tx_result?.code}, log: ${(txResult as any).tx_result?.log}`);
- console.log(`DEBUG Attempt ${i + 1}: getSignedAttestation returned:`, {/* ... */});
+ if (process.env.DEBUG) console.log(`DEBUG Attempt ${i + 1}: getSignedAttestation returned:`, {/* ... */});

Also applies to: 90-106


155-161: Avoid unbounded result sets in tests

listAttestations({}) uses the default (5000). Specify a small limit for faster, deterministic tests.

-const attestations = await attestationAction.listAttestations({});
+const attestations = await attestationAction.listAttestations({ limit: 25 });
src/contracts-api/attestationAction.ts (3)

236-246: Prefer Either.throw() and lower the default limit

Use throw() for clarity and reduce default limit to avoid large responses by default. Keep 5000 as an explicit opt‑in.

-    const limit = input.limit ?? 5000;
+    const limit = input.limit ?? 100;
@@
-    const result = await this.call<any[]>('list_attestations', params);
-
-    // Check for errors
-    if (result.isLeft()) {
-      throw new Error(
-        `Failed to list attestations: HTTP status ${result.value}`
-      );
-    }
-
-    // Extract the right value from Either
-    const rows = result.value as unknown as any[];
+    const result = await this.call<any[]>('list_attestations', params);
+    const rows = result.throw();

Also applies to: 249-260


317-324: Add length checks for decoded BYTEA fields

Catch malformed rows early and surface clear errors.

   return {
-    requestTxId,
-    attestationHash,
-    requester,
+    requestTxId,
+    attestationHash: (() => {
+      if (attestationHash.length !== 32) {
+        throw new Error(`Row ${idx}: attestation_hash must be 32 bytes, got ${attestationHash.length}`);
+      }
+      return attestationHash;
+    })(),
+    requester: (() => {
+      if (requester.length !== 20) {
+        throw new Error(`Row ${idx}: requester must be 20 bytes, got ${requester.length}`);
+      }
+      return requester;
+    })(),
     createdHeight,
     signedHeight,
     encryptSig,
   };

340-359: Optionally accept hex‑encoded BYTEA strings

Some backends return “\x…” or “0x…” hex. Supporting these formats improves robustness across environments.

   if (typeof value === 'string') {
     try {
+      // Hex formats: \x... (PG) or 0x... (generic)
+      if (/^\\x[0-9a-fA-F]+$/.test(value)) {
+        const hex = value.slice(2);
+        const out = new Uint8Array(hex.length / 2);
+        for (let i = 0; i < hex.length; i += 2) out[i / 2] = parseInt(hex.substr(i, 2), 16);
+        return out;
+      }
+      if (/^0x[0-9a-fA-F]+$/.test(value)) {
+        const hex = value.slice(2);
+        const out = new Uint8Array(hex.length / 2);
+        for (let i = 0; i < hex.length; i += 2) out[i / 2] = parseInt(hex.substr(i, 2), 16);
+        return out;
+      }
       // Node.js environment
       if (typeof Buffer !== 'undefined') {
         return new Uint8Array(Buffer.from(value, 'base64'));
       } else {
src/types/attestation.ts (2)

202-221: Simplify orderBy validation (case‑insensitive) and avoid duplicates

Normalize to lowercase and check against a canonical list.

-const VALID_ORDER_BY_VALUES = [
-  'created_height asc',
-  'created_height desc',
-  'signed_height asc',
-  'signed_height desc',
-  'created_height ASC',
-  'created_height DESC',
-  'signed_height ASC',
-  'signed_height DESC',
-];
+const VALID_ORDER_BY_VALUES = [
+  'created_height asc',
+  'created_height desc',
+  'signed_height asc',
+  'signed_height desc',
+];
@@
-export function isValidOrderBy(orderBy: string): boolean {
-  return VALID_ORDER_BY_VALUES.includes(orderBy);
-}
+export function isValidOrderBy(orderBy: string): boolean {
+  return VALID_ORDER_BY_VALUES.includes(orderBy.toLowerCase());
+}

160-197: validateAttestationRequest: solid; consider adding stricter streamId check (optional)

If stream IDs have a fixed alphabet/prefix, validate format (not just length) to fail fast on typos.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1dcae and daed44f.

📒 Files selected for processing (12)
  • README.md (2 hunks)
  • examples/attestation/README.md (1 hunks)
  • examples/attestation/index.ts (1 hunks)
  • src/client/client.ts (2 hunks)
  • src/contracts-api/action.ts (2 hunks)
  • src/contracts-api/attestationAction.ts (1 hunks)
  • src/index.common.ts (1 hunks)
  • src/internal.ts (2 hunks)
  • src/types/attestation.ts (1 hunks)
  • src/util/AttestationEncoding.ts (1 hunks)
  • tests/integration/attestation.test.ts (1 hunks)
  • tests/integration/trufnetwork.setup.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/contracts-api/attestationAction.ts (3)
src/contracts-api/action.ts (1)
  • Action (72-1080)
src/types/attestation.ts (8)
  • RequestAttestationInput (12-48)
  • RequestAttestationResult (53-59)
  • validateAttestationRequest (160-197)
  • GetSignedAttestationInput (64-69)
  • SignedAttestationResult (74-83)
  • ListAttestationsInput (88-117)
  • AttestationMetadata (122-152)
  • validateListAttestationsInput (229-259)
src/util/AttestationEncoding.ts (1)
  • encodeActionArgs (22-65)
src/client/client.ts (3)
src/contracts-api/attestationAction.ts (1)
  • AttestationAction (53-269)
src/index.common.ts (1)
  • AttestationAction (31-31)
src/internal.ts (1)
  • AttestationAction (15-15)
src/types/attestation.ts (2)
src/index.common.ts (6)
  • RequestAttestationInput (33-33)
  • RequestAttestationResult (34-34)
  • GetSignedAttestationInput (35-35)
  • SignedAttestationResult (36-36)
  • ListAttestationsInput (37-37)
  • AttestationMetadata (38-38)
src/internal.ts (6)
  • RequestAttestationInput (61-61)
  • RequestAttestationResult (62-62)
  • GetSignedAttestationInput (63-63)
  • SignedAttestationResult (64-64)
  • ListAttestationsInput (65-65)
  • AttestationMetadata (66-66)
tests/integration/attestation.test.ts (2)
tests/integration/trufnetwork.setup.ts (1)
  • setupTrufNetwork (251-297)
tests/integration/utils.ts (1)
  • testWithDefaultWallet (9-11)
🪛 markdownlint-cli2 (0.18.1)
examples/attestation/README.md

52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


94-94: Hard tabs
Column: 1

(MD010, no-hard-tabs)


102-102: Hard tabs
Column: 1

(MD010, no-hard-tabs)


128-128: Hard tabs
Column: 1

(MD010, no-hard-tabs)


129-129: Hard tabs
Column: 1

(MD010, no-hard-tabs)


130-130: Hard tabs
Column: 1

(MD010, no-hard-tabs)


152-152: Hard tabs
Column: 1

(MD010, no-hard-tabs)


153-153: Hard tabs
Column: 1

(MD010, no-hard-tabs)


154-154: Hard tabs
Column: 1

(MD010, no-hard-tabs)


155-155: Hard tabs
Column: 1

(MD010, no-hard-tabs)


156-156: Hard tabs
Column: 1

(MD010, no-hard-tabs)


157-157: Hard tabs
Column: 1

(MD010, no-hard-tabs)


158-158: Hard tabs
Column: 1

(MD010, no-hard-tabs)


159-159: Hard tabs
Column: 1

(MD010, no-hard-tabs)


160-160: Hard tabs
Column: 1

(MD010, no-hard-tabs)


161-161: Hard tabs
Column: 1

(MD010, no-hard-tabs)


162-162: Hard tabs
Column: 1

(MD010, no-hard-tabs)


163-163: Hard tabs
Column: 1

(MD010, no-hard-tabs)


168-168: Hard tabs
Column: 1

(MD010, no-hard-tabs)


169-169: Hard tabs
Column: 1

(MD010, no-hard-tabs)


174-174: Hard tabs
Column: 1

(MD010, no-hard-tabs)


185-185: Hard tabs
Column: 1

(MD010, no-hard-tabs)


186-186: Hard tabs
Column: 1

(MD010, no-hard-tabs)


191-191: Hard tabs
Column: 1

(MD010, no-hard-tabs)


192-192: Hard tabs
Column: 1

(MD010, no-hard-tabs)


216-216: Hard tabs
Column: 1

(MD010, no-hard-tabs)


217-217: Hard tabs
Column: 1

(MD010, no-hard-tabs)


218-218: Hard tabs
Column: 1

(MD010, no-hard-tabs)


219-219: Hard tabs
Column: 1

(MD010, no-hard-tabs)


220-220: Hard tabs
Column: 1

(MD010, no-hard-tabs)


221-221: Hard tabs
Column: 1

(MD010, no-hard-tabs)


222-222: Hard tabs
Column: 1

(MD010, no-hard-tabs)


223-223: Hard tabs
Column: 1

(MD010, no-hard-tabs)


224-224: Hard tabs
Column: 1

(MD010, no-hard-tabs)


225-225: Hard tabs
Column: 1

(MD010, no-hard-tabs)


226-226: Hard tabs
Column: 1

(MD010, no-hard-tabs)


227-227: Hard tabs
Column: 1

(MD010, no-hard-tabs)


228-228: Hard tabs
Column: 1

(MD010, no-hard-tabs)


245-245: Hard tabs
Column: 1

(MD010, no-hard-tabs)


246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


267-267: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (8)
tests/integration/trufnetwork.setup.ts (1)

41-41: Entrypoint/path compatibility

You override entrypoint to /app/kwild. Confirm this path exists for all intended images/tags; otherwise the container fails immediately. Consider making ENTRYPOINT override configurable or omit it when the image already sets it correctly.

Also applies to: 268-269

src/contracts-api/action.ts (2)

90-109: JSDoc improvements look good

Clearer contract and return docs for executeWithNamedParams.


110-121: New executeWithActionBody helper is fine

Signature and pass-through behavior to kwilClient.execute are consistent.

src/internal.ts (1)

15-15: New exports wired correctly

AttestationAction and attestation types are cleanly re-exported; no circular hints here.

Also applies to: 59-67

src/index.common.ts (1)

30-40: Public surface extension looks good

AttestationAction and types are exposed coherently with the rest of the API.

README.md (1)

429-431: Quick reference additions look good

API signatures match the public surface in code and tests.

src/contracts-api/attestationAction.ts (1)

85-118: requestAttestation flow LGTM

Validation, encoding, and tx_hash handling are sound.

src/types/attestation.ts (1)

261-476: Inline tests are helpful and focused

Good coverage of validation paths and orderBy checks.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daed44f and 0284328.

📒 Files selected for processing (1)
  • package.json (1 hunks)

@MicBun MicBun requested a review from outerlook October 21, 2025 18:14
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/integration/trufnetwork.setup.ts (1)

88-102: Add .toString() for Buffer-to-string conversion in error handling.

When execFile throws an error, the error.stdout and error.stderr properties may be Buffers rather than strings. Directly returning or concatenating them can cause type issues.

Apply this diff to safely convert Buffers to strings:

 async function runDockerCommand(args: string[], check = false) {
   // Use execFile with argument array to prevent shell injection
   try {
     const { stdout, stderr } = await execFile("docker", args);
     if (stderr) {
       console.debug(stderr);
     }
     return { stdout, stderr };
   } catch (error: any) {
     if (check) {
-      throw new Error(error.stderr || error.message);
+      throw new Error(error.stderr?.toString() || error.message);
     }
-    return { stdout: error.stdout || "", stderr: error.stderr || error.message };
+    return {
+      stdout: error.stdout?.toString() || "",
+      stderr: error.stderr?.toString() || error.message
+    };
   }
 }
🧹 Nitpick comments (2)
src/contracts-api/attestationAction.ts (1)

153-161: Consider adding hex format validation for requestTxId.

The normalization logic correctly strips the optional 0x prefix and lowercases the input, but it doesn't validate that the result is exactly 64 hexadecimal characters. This could allow malformed transaction IDs to be passed to the backend.

Add validation after normalization:

 // Strip optional "0x" prefix and lowercase for normalization
 const normalizedRequestTxId = trimmed.startsWith('0x')
   ? trimmed.slice(2).toLowerCase()
   : trimmed.toLowerCase();
+
+// Validate hex format (64 characters)
+if (!/^[0-9a-f]{64}$/.test(normalizedRequestTxId)) {
+  throw new Error('requestTxId must be 64 hex characters (optional 0x prefix)');
+}
src/types/attestation.ts (1)

202-221: Consider case-insensitive validation to simplify the whitelist.

The current approach explicitly lists both uppercase and lowercase variants, which works correctly but is redundant. A case-insensitive comparison would be more concise.

 const VALID_ORDER_BY_VALUES = [
   'created_height asc',
   'created_height desc',
   'signed_height asc',
   'signed_height desc',
-  'created_height ASC',
-  'created_height DESC',
-  'signed_height ASC',
-  'signed_height DESC',
 ];

 export function isValidOrderBy(orderBy: string): boolean {
-  return VALID_ORDER_BY_VALUES.includes(orderBy);
+  return VALID_ORDER_BY_VALUES.includes(orderBy.toLowerCase());
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0284328 and 6811f1a.

📒 Files selected for processing (3)
  • src/contracts-api/attestationAction.ts (1 hunks)
  • src/types/attestation.ts (1 hunks)
  • tests/integration/trufnetwork.setup.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/types/attestation.ts (2)
src/index.common.ts (6)
  • RequestAttestationInput (33-33)
  • RequestAttestationResult (34-34)
  • GetSignedAttestationInput (35-35)
  • SignedAttestationResult (36-36)
  • ListAttestationsInput (37-37)
  • AttestationMetadata (38-38)
src/internal.ts (6)
  • RequestAttestationInput (61-61)
  • RequestAttestationResult (62-62)
  • GetSignedAttestationInput (63-63)
  • SignedAttestationResult (64-64)
  • ListAttestationsInput (65-65)
  • AttestationMetadata (66-66)
src/contracts-api/attestationAction.ts (3)
src/contracts-api/action.ts (1)
  • Action (72-1080)
src/types/attestation.ts (8)
  • RequestAttestationInput (12-48)
  • RequestAttestationResult (53-59)
  • validateAttestationRequest (160-197)
  • GetSignedAttestationInput (64-69)
  • SignedAttestationResult (74-83)
  • ListAttestationsInput (88-117)
  • AttestationMetadata (122-152)
  • validateListAttestationsInput (229-259)
src/util/AttestationEncoding.ts (1)
  • encodeActionArgs (22-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (15)
tests/integration/trufnetwork.setup.ts (4)

2-2: LGTM! Correct import and promisification.

The addition of execFile import and its promisification correctly sets up the safer command execution mechanism.

Also applies to: 8-8


51-54: Good practice to validate environment input.

The validation call correctly prevents shell injection by checking the image name before use. Once the regex in validateDockerImage is fixed (see previous comment), this will provide robust protection.


58-58: LGTM! Correct usage of validated image.

The container spec correctly uses the validated TN_DB_IMAGE variable, enabling flexible image selection while maintaining security.


287-287: LGTM! Improved logging for debugging.

Including the image name in the log message provides useful visibility into which Docker image is being used during test setup.

src/contracts-api/attestationAction.ts (7)

1-53: LGTM!

The imports are well-organized, and the class documentation provides clear examples demonstrating the attestation workflow.


85-118: LGTM!

The method correctly validates input, encodes arguments, executes the attestation request, and returns the transaction hash as requestTxId.


164-208: LGTM!

The method correctly calls the view action with the normalized ID, handles Either results properly, validates the response structure, and includes cross-platform base64 decoding for both Node.js and browser environments.


236-274: LGTM!

The method correctly validates input, applies sensible defaults, handles Either results, and maps rows to AttestationMetadata using the helper function.


288-331: LGTM!

The parser robustly handles both array and object row formats, provides clear error messages with row context, and correctly processes nullable fields.


336-371: LGTM!

The helper function provides robust decoding with cross-platform support, comprehensive type checking, and informative error messages including row and column context.


374-440: LGTM!

The inline tests provide good coverage of helper functions, testing both happy paths and error conditions for base64 decoding and row parsing in both array and object formats.

src/types/attestation.ts (4)

1-152: LGTM!

The interface definitions are comprehensive and well-documented, providing clear guidance on expected formats, constraints, and examples for each field.


160-197: LGTM!

The validation function thoroughly checks all input parameters with appropriate error messages. The use of snake_case in error messages (e.g., data_provider, stream_id) appears intentional for consistency with backend parameter names.


251-258: LGTM!

The orderBy validation correctly uses the isValidOrderBy function and provides a helpful error message listing the valid options.


262-476: Good test coverage overall.

The inline tests comprehensively cover validation functions with both valid inputs and various error conditions. Note that the requester validation test issue has been flagged separately in the previous comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6811f1a and b0a0057.

📒 Files selected for processing (2)
  • src/types/attestation.ts (1 hunks)
  • tests/integration/trufnetwork.setup.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/trufnetwork.setup.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/types/attestation.ts (2)
src/index.common.ts (6)
  • RequestAttestationInput (33-33)
  • RequestAttestationResult (34-34)
  • GetSignedAttestationInput (35-35)
  • SignedAttestationResult (36-36)
  • ListAttestationsInput (37-37)
  • AttestationMetadata (38-38)
src/internal.ts (6)
  • RequestAttestationInput (61-61)
  • RequestAttestationResult (62-62)
  • GetSignedAttestationInput (63-63)
  • SignedAttestationResult (64-64)
  • ListAttestationsInput (65-65)
  • AttestationMetadata (66-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (4)
src/types/attestation.ts (4)

1-152: LGTM: Well-structured type definitions.

The attestation type interfaces are comprehensive and well-documented with clear constraints and examples. The use of any[] for action arguments (line 35) provides the necessary flexibility for different action signatures.


160-197: LGTM: Comprehensive validation logic.

The validation function properly validates all input fields with appropriate error messages. The checks for dataProvider format (0x-prefixed 40 hex chars), streamId length (32 chars), and other constraints are correct and thorough.


229-259: LGTM: Validation logic is correct.

The validation properly checks all optional fields with appropriate constraints. The requester validation at lines 244-249 correctly requires exactly 20 bytes, which aligns with Ethereum address requirements.

Note: The error message at line 255 shows only the first 4 values using .slice(0, 4) to avoid displaying the redundant uppercase duplicates. If the orderBy refactor (suggested in previous comment) is applied, this slice operation can be removed.


261-484: LGTM: Comprehensive test coverage, past review comment addressed.

The unit tests provide excellent coverage of all validation paths:

  • validateAttestationRequest: 10 test cases covering all validation branches
  • isValidOrderBy: 3 test cases for valid/invalid inputs
  • validateListAttestationsInput: 9 test cases covering all constraints

The past review comment has been properly addressed—lines 460-474 now include tests for both requester lengths that are too long (21 bytes) and too short (19 bytes), both correctly expecting "requester must be exactly 20 bytes".

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/types/attestation.ts (2)

35-35: Consider documenting the type safety trade-off for action arguments.

The any[] type for args is necessary given that action signatures vary, but it means type safety for arguments is deferred to runtime. Consider adding a brief comment explaining this trade-off or referencing where argument validation occurs.


253-253: Remove unnecessary .slice(0, 4) call.

Since VALID_ORDER_BY_VALUES contains exactly 4 elements (lines 203-206), the slice is redundant. Moreover, if more values are added in the future, the error message should show all valid options, not just the first 4.

Apply this diff:

-        `Invalid orderBy value. Must be one of: ${VALID_ORDER_BY_VALUES.slice(0, 4).join(', ')}`
+        `Invalid orderBy value. Must be one of: ${VALID_ORDER_BY_VALUES.join(', ')}`
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0a0057 and 702000d.

📒 Files selected for processing (1)
  • src/types/attestation.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/types/attestation.ts (2)
src/index.common.ts (6)
  • RequestAttestationInput (33-33)
  • RequestAttestationResult (34-34)
  • GetSignedAttestationInput (35-35)
  • SignedAttestationResult (36-36)
  • ListAttestationsInput (37-37)
  • AttestationMetadata (38-38)
src/internal.ts (6)
  • RequestAttestationInput (61-61)
  • RequestAttestationResult (62-62)
  • GetSignedAttestationInput (63-63)
  • SignedAttestationResult (64-64)
  • ListAttestationsInput (65-65)
  • AttestationMetadata (66-66)
🔇 Additional comments (4)
src/types/attestation.ts (4)

160-197: LGTM! Comprehensive validation with one minor note.

The validation logic is thorough and correct. The error messages use snake_case field names (data_provider, stream_id, action_name, max_fee) while the TypeScript properties are camelCase. This is likely intentional to match backend field names for easier debugging, but worth confirming consistency across the codebase.


215-219: Excellent fix! True case-insensitive validation now implemented.

The previous review comment has been properly addressed. The implementation now normalizes input to lowercase and validates against a lowercase-only whitelist, providing true case-insensitive matching.


465-479: Excellent! Previous review feedback properly addressed.

Both test cases for requester validation have been correctly updated to match the new validation logic that requires exactly 20 bytes. The tests now properly check both the "too long" (21 bytes) and "too short" (19 bytes) cases with the correct error message.


395-423: Great test coverage for case-insensitive orderBy validation.

The tests thoroughly validate the case-insensitive behavior, including lowercase, uppercase, mixed case, and whitespace trimming. This provides excellent coverage for the implementation at lines 215-219.

@MicBun MicBun merged commit 93e7768 into main Oct 22, 2025
5 of 6 checks passed
@MicBun MicBun deleted the workingAttest branch October 22, 2025 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants