Skip to content

Conversation

@PsicoThePato
Copy link
Contributor

@PsicoThePato PsicoThePato commented Sep 13, 2025

Summary by CodeRabbit

  • New Features

    • Added StarkNet wallet support in Torus Bridge.
    • Introduced Wallet Stake Permission: create and execute flows in Torus Portal, with new page, form, and permission details/selector entries.
    • Improved Torus Bridge bundling (vendor chunking) and YAML config support.
  • Bug Fixes

    • Clearer validation errors across portal forms.
    • Revoke transaction status progression fixed.
    • Transfer flow shows persistent toast guiding manual network switch.
  • Documentation

    • Testing docs restructured: chain-query vs chain-tx categories.
  • Tests

    • Split chain tests into chain-query and chain-tx; new scripts and turbo tasks.
  • Chores

    • Dependency upgrades and new git hooks/Justfile utilities.

…s-bridge app; include new versions for cosmos-kit, hyperlane, solana, starknet-react, and others. Add reference path for routes in next-env.d.ts.
…imports and streamlining function definition in torus-bridge app
…ferEVM component of torus-bridge app for improved security
…act/core and @starknet-react/chains for Starknet integration in torus-bridge app
…ing unused packages and enhancing webpack settings for improved performance
…ng transpile packages and removing unused settings for better maintainability
…rk/torus-utils for better dependency management in torus-bridge app
… in torus-bridge app to enhance performance and user experience
…p to enhance performance and user experience
… in torus-bridge app to enhance performance and user experience
…us-bridge app to ensure proper functionality
…timizations and improved dynamic imports for better performance
- Align pnpm cache paths between CI/CD and Docker for maximum reuse
- GitHub Actions: ~/.local/share/pnpm/store/v3
- Docker: /root/.local/share/pnpm/store/v3 (same path)
- Add .next/cache to GitHub Actions cache for additional performance
- Set PNPM_HOME to match GitHub Actions cache directory in Docker

Expected performance improvements:
- First build: Creates cache (~27s)
- Subsequent builds: Reuse cache (~5-10s for pnpm install)
- Docker builds: Use pre-warmed cache from CI/CD (~5-10s vs ~27s)
- Total time reduction: 45-60% faster builds
…turing deployment and PR preview jobs for improved clarity and organization
… component of torus-bridge app for cleaner code
…d build performance

- Consolidate pnpm cache setup across all apps in GitHub Actions
- Enhance caching strategy for node_modules to reduce installation time
- Update Dockerfile to utilize pnpm's offline installation for faster builds
- Add cache hit/miss information to GitHub Actions summary for better visibility
…prove deployment clarity

- Reintroduce torus-bridge deployment and PR preview jobs in the workflow
- Optimize job dependencies and conditions for better organization
- Remove redundant pnpm cache setup to streamline the workflow
- Eliminate redundant pnpm cache steps to streamline the workflow
- Update dependency installation logic for improved clarity
- Enhance job summary output for better visibility of triggers
…torus-bridge and root

- Downgrade @starknet-react/core to version 3.7.2
- Downgrade @starknet-react/chains to version 4.0.4
- Upgrade starknet to version 6.24.1 in root package.json
- Adjust whitespace in the GitHub Actions workflow for consistency
- Ensure Dockerfile ends with a newline for better compatibility
steinerkelvin and others added 25 commits September 8, 2025 18:33
- Move Api type and SbQueryError from fees.ts to dedicated modules
- Consolidate all base types from types/base.ts into chain/common/types.ts
- Create dedicated chain/common/errors.ts for error types
- Remove redundant sb_amount schema and Amount type
- Simplify sb_balance to use sb_bigint directly without nonnegative constraint
- Update governance cost field to use sb_bigint instead of sb_amount
- Fix imports across 8+ files for reorganized type locations
- Maintain backward compatibility through re-exports in types/index.ts

BREAKING: Types previously imported from types/base.ts must now be imported
from types/index.ts (re-exported) or directly from chain/common/types.ts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace .errors with .issues in validation error handling across portal
constraint components and SDK metadata validation to prepare for Zod v4
migration where the errors property was renamed to issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add madge-dependencies-graph and madge-circular commands in justfile
with descriptions of their purpose and output behavior.

- madge-dependencies-graph: Generate dependency graph visualization
  of source code files as SVG;
- madge-circular: Detect circular dependencies in the codebase;

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Move Zod-based Substrate type parsers from scattered locations into a cohesive
`substrate-parsers/` module with logical organization by functionality:

- **Collections**: Arrays, Sets, BTreeSet, Map parsing (collections.ts)
- **Primitives**: Null, bool, numbers, bigint parsing (primitives.ts)
- **Text/Bytes**: String/Text and Bytes parsing (text.ts, bytes.ts)
- **Advanced Types**: Hash, Address, Option, Struct, Enum parsing
- **Helpers**: ToPrimitive/ToBigInt interfaces for conversion utilities

Key improvements:
- Consolidate scattered parser logic into organized, discoverable modules
- Add comprehensive JSDoc documentation with usage examples
- Introduce new `sb_basic_enum` parser for simple enumeration types
- Centralized re-exports through substrate-parsers/index.ts
- Update all import paths to use new module structure

This refactoring improves code organization and developer experience while
maintaining full API compatibility for existing Substrate type parsing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Move permission event schemas from types/events.ts to chain/permission0/permission0-events.ts
for better organization. Replace z.string().pipe(SS58_SCHEMA) with sb_address for consistency
with substrate parser patterns. Remove redundant parse wrapper functions in favor of direct
.safeParse() usage, simplifying the API and reducing unnecessary abstraction layers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Move PermissionAccumulationToggledEvent, PermissionExpiredEvent, and
PermissionRevokedEvent exports from the generic types module to the
domain-specific chain module for better organization.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace simple z.record(z.unknown()) with detailed Zod schemas for GlobalParams
and NamespacePricingConfig, including business logic validation constraints that
match Substrate pallet requirements. Enable previously commented test assertions
to validate GlobalParams proposal structure.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Comment out WalletConflictGuard in the RootLayout component for potential future use
- Update documentation link in TransferEVM component to point to the correct guide for bridging from Base
- Update versions for various dependencies to ensure compatibility
- Remove conflicting entries and streamline dependency resolutions
Splits chain tests into two distinct vitest projects for better performance:

- `test:chain-query`: Fast read-only tests (storages.test.ts) ~2s
- `test:chain-tx`: Slower transaction tests (extrinsics*.test.ts) ~67s
- `test:chain`: Runs both projects sequentially

Changes:
- Rename `chain/` → `chain-query/` directory for consistency
- Move transaction tests to new `chain-tx/` directory
- Update vitest config with separate projects and timeouts
- Add new test commands to package.json
- Update Turborepo config with proper task dependencies
- Enhance README documentation with test type details

This allows developers to run fast query tests during development while
keeping slower transaction tests separate for comprehensive testing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Move agent-related test files from root __tests__ directory to __tests__/agent/:
- test-server.ts, test-server-auth.ts - Agent server implementations
- test-client.ts, test-client-auth.ts - Agent client implementations
- run-test.ts, run-auth-test.ts - Integration test runners
- python-client/ - Cross-language client tests

Update import paths and file references to maintain functionality.
Improves test organization by grouping related agent functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Adds a pre-push git hook that automatically runs `just format-fix` to ensure
code formatting standards are maintained for developers using the Nix
development environment.

Changes:
- Add git-hooks.nix flake input and dependency
- Configure pre-push hook to run format-fix command
- Update .gitignore to exclude .pre-commit-config.yaml

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add pre-push git hook for code formatting (Nix env)
refactor: reorganize substrate parsers and test structure
…-bridge

fix: broken switchChain issues & update deps
@PsicoThePato PsicoThePato requested a review from a team as a code owner September 13, 2025 13:44
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 13, 2025

Walkthrough

Adds wallet-stake permissions across SDK, interfaces, and portal UI; introduces Substrate parser modules and refactors type/errors exports; moves permission events to chain; updates governance/global params; adds balance query and query-provider hook; reworks Next.js config and providers (incl. Starknet) in torus-bridge; splits chain tests (query/tx); adjusts worker handling; tooling updates (Nix hooks, turbo tasks, Just recipes).

Changes

Cohort / File(s) Summary
Repo tooling & CI
flake.nix, justfile, turbo.json, package.json, .gitignore, .pre-commit-config.yaml, README.md, packages/torus-sdk-ts/vitest.config.ts, packages/torus-sdk-ts/package.json
Adds Nix git-hooks and devShell hook; new Just recipes for Madge graphs/cycles; splits test:chain into query/tx in turbo and vitest; removes madge devDep; adds StarkNet overrides; ignores root pre-commit config; README test categories updated.
Bridge app config & deps
apps/torus-bridge/next.config.mjs, apps/torus-bridge/package.json, apps/torus-bridge/next-env.d.ts
Reworks Next config: optimizePackageImports, YAML loader (webpack/turbopack), splitChunks; adds transpile package; updates deps (Cosmos/Hyperlane/Solana; adds StarkNet); adds route types reference.
Bridge providers & UX
apps/torus-bridge/src/app/_components/provider-renderer.tsx, apps/torus-bridge/src/context/starknet-wallet-provider.tsx, apps/torus-bridge/src/context/app-context-provider.tsx, apps/torus-bridge/src/app/layout.tsx, apps/torus-bridge/src/app/_components/transfer-evm.tsx, apps/torus-bridge/src/hooks/use-token-transfer.ts
Adds ProviderRenderer and StarknetWalletProvider; composes providers via array; comments out WalletConflictGuard; tweaks EVM withdraw toast behavior; maps Revoke tx statuses.
Portal: Zod issues migration
apps/torus-portal/.../constraint-node-base.tsx, .../constraint-node-number.tsx, .../constraint-node-permission-id.tsx, .../constraint-utils.ts, .../register-agent-utils.ts, packages/torus-sdk-ts/src/metadata/metadata.ts, packages/torus-sdk-ts/src/__tests__/chain-query/storages.test.ts
Switches error access from .errors to .issues across validations and tests.
Portal: Wallet permissions (create/manage)
apps/torus-portal/.../wallet/_components/wallet-stake-permission-form.tsx, .../wallet/_components/wallet-stake-permission-schema.ts, .../wallet/page.tsx, .../manage-permission/_components/edit-permission-form.tsx, .../manage-permission/_components/execute-wallet-fields/*, .../manage-permission/_components/permission-type-info.tsx, apps/torus-portal/src/app/_components/permission-selector/*, apps/torus-portal/src/app/_components/sidebar/app-sidebar.tsx, apps/torus-portal/src/app/_components/permission-selector/permission-selector.utils.ts
Adds wallet-stake creation form/schema/page; adds execute wallet form (unstake/transfer), fields, and token input; extends permission selector/details and type info for Wallet; adds sidebar link; updates utils to include wallet permissions and roles.
Worker
apps/torus-worker/src/workers/agent-fetcher.ts
Adds Wallet scope handling: skip processing and no children.
SDK: Parsers split & types/errors
packages/torus-sdk-ts/src/substrate-parsers/*, packages/torus-sdk-ts/src/types/index.ts, packages/torus-sdk-ts/src/types/address.ts, packages/torus-sdk-ts/src/types/zod.ts (removed), packages/torus-sdk-ts/src/types/base.ts (removed), packages/torus-sdk-ts/src/types/events.ts (removed), packages/torus-sdk-ts/src/chain/common/{types.ts,errors.ts}
Introduces modular Substrate parsers (primitives, struct, enum, option, collections, text, bytes, hash, address, helpers, index); adds SS58 branded type and validators; adds Api/numeric types and SbQueryError; removes legacy zod/base/events modules; updates type export surface.
SDK: Chain exports & events
packages/torus-sdk-ts/src/chain/index.ts, packages/torus-sdk-ts/src/chain/permission0/permission0-events.ts, packages/dsl/src/chain-watcher.ts
Re-exports common types/errors/parsers; adds permission0 event schemas; chain-watcher now uses new event schemas via safeParse.
SDK: Governance & balances
packages/torus-sdk-ts/src/chain/governance/governance-types.ts, packages/torus-sdk-ts/src/chain/balances.ts
Adds GlobalParams/NamespacePricingConfig schemas; updates Emission variant; adds queryBalance (free/reserved→staked).
SDK: Fees/types refactor
packages/torus-sdk-ts/src/chain/common/fees.ts, packages/torus-sdk-ts/src/chain/{emission0,governance,torus0}/*-storage.ts, packages/torus-sdk-ts/src/chain/rpc.ts, packages/torus-sdk-ts/src/chain/common/delegation-tree-builder.ts
Moves Api/SbQueryError to new modules; adds brokenQueryExtFeeInfo; updates imports across storage/rpc/tree-builder.
SDK: Wallet permission scope
packages/torus-sdk-ts/src/chain/permission0/{permission0-types.ts,permission0-extrinsics.ts,permission0-storage.ts}, packages/torus-sdk-ts/src/agent/agent.ts
Adds Wallet scope/types/operations; new extrinsics (delegate/execute wallet stake); storage/queries track walletPermissions and recipients; agent loader skips Wallet in namespace delegation.
SDK: Interfaces/types registry
packages/torus-sdk-ts/src/interfaces/{lookup.ts,types-lookup.ts,augment-api-tx.ts,augment-api-errors.ts,registry.ts}
Extends metadata for Wallet scope/extrinsics/types and new error StakeIsDelegated; exposes new interfaces in registry.
Query provider
packages/query-provider/src/hooks.ts
Adds useBalance hook; logs errors via console.log in useAllPermissions.
Tests & runners
packages/torus-sdk-ts/src/__tests__/agent/*, packages/torus-sdk-ts/src/main.ts
Moves agent test server paths under agent/; refactors main to log queryAllPermissions only.
Cache app
apps/torus-cache/package.json
Adds node-fetch dependency.

Sequence Diagram(s)

sequenceDiagram
  actor U as User
  participant P as Portal UI
  participant SDK as SDK (permission0)
  participant CH as Chain (permission0 pallet)

  U->>P: Fill Wallet Stake Permission form
  P->>SDK: delegateWalletStakePermission(recipient, stakeDetails, duration, revocation)
  SDK->>CH: extrinsic permission0.delegate_wallet_stake_permission
  CH-->>SDK: InBlock / Finalized events
  SDK-->>P: Tx result (tracker)
  P-->>U: Show success/failure
Loading
sequenceDiagram
  actor U as User
  participant P as Manage Permission UI
  participant SDK as SDK (permission0)
  participant CH as Chain

  U->>P: Choose operation (Unstake or Transfer)
  P->>SDK: executeWalletStakePermission(permissionId, op)
  SDK->>CH: extrinsic permission0.execute_wallet_stake_permission
  CH-->>SDK: InBlock / Finalized events
  SDK-->>P: Tx result (tracker)
  P-->>U: Update UI/status
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Possibly related PRs

Suggested labels

needs-review

Suggested reviewers

  • EdSDR
  • steinerkelvin
  • Sipher

Poem

In parsley-green commits I leap with delight,
New wallets and parsers hop into sight.
I nudge the chain—delegate, execute—go!
Tests split like clover, two rows in a row.
With YAML and hooks, I twitch my nose—
Carrots for CI; onward it goes! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.73% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The current PR title "Dev" is too short and generic to convey the primary change and does not summarize the main scope of this large changeset. The diff contains substantial, reviewer-relevant changes (wallet-stake permission support across torus-sdk-ts and torus-portal, new substrate-parsers and types, Next.js config and dependency updates, provider refactor, etc.), so "Dev" does not allow a teammate scanning history to understand the primary change. Because the title is vague and non-descriptive, the check is inconclusive. Please rename the PR to a concise, descriptive title that highlights the primary change (for example, "Add wallet-stake permission API and UI" or "Introduce wallet-stake permissions, substrate-parsers, and provider refactor"). Prefer a short sentence with an optional scope prefix (e.g., "packages/torus-sdk-ts: Add wallet-stake permission") and avoid generic terms like "Dev" or "Misc". If multiple equally important changes exist, choose the single most reviewer-relevant change for the title and summarize secondary changes in the PR description.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 YAMLlint (1.37.1)
.pre-commit-config.yaml

[Errno 2] No such file or directory: '.pre-commit-config.yaml'

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@PsicoThePato PsicoThePato merged commit 2a1f875 into main Sep 13, 2025
99 of 100 checks passed
Copy link
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: 37

Caution

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

⚠️ Outside diff range comments (24)
packages/torus-sdk-ts/src/__tests__/agent/test-client-auth.ts (1)

42-43: Fix ESM/CJS interop: don’t use base64url.default.*.

In Node ESM, import base64url from "base64url" yields the module on the default export; accessing .default breaks at runtime.

Apply:

-  const encodedHeader = base64url.default.encode(JSON.stringify(header));
-  const encodedPayload = base64url.default.encode(JSON.stringify(payload));
+  const encodedHeader = base64url.encode(JSON.stringify(header));
+  const encodedPayload = base64url.encode(JSON.stringify(payload));
@@
-  const encodedSignature = base64url.default.encode(Buffer.from(signature));
+  const encodedSignature = base64url.encode(Buffer.from(signature));

Also applies to: 57-57

packages/torus-sdk-ts/src/__tests__/agent/test-server-auth.ts (1)

71-82: “Non-existent namespace” test isn’t actually non-existent

Comment says the namespace “won’t exist,” but rpcUrls points to a real endpoint. This can produce false negatives in permission tests.

Proposed fix: use an intentionally invalid URL to guarantee failure.

-    namespace: {
-      enabled: true,
-      rpcUrls: ["wss://api.testnet.torus.network"],
-    },
+    namespace: {
+      enabled: true,
+      // Intentionally invalid URL to ensure this namespace cannot resolve
+      rpcUrls: ["wss://does-not-exist.invalid"],
+    },

Optionally, fail fast in the handler to make any accidental reachability loudly fail:

-  async (input) => {
-    // This should never be reached if namespace permissions are working
-    return {
-      ok: {
-        result: `This shouldn't happen - received: ${input.message}`,
-      },
-    };
-  },
+  async () => {
+    throw new Error("restricted-namespace-test-el-psy-congroo should be unreachable when permissions are enforced");
+  },

Also applies to: 101-108

packages/torus-sdk-ts/src/chain/torus0/torus0-storage.ts (1)

26-48: Return Result<T,E> instead of throwing; align with chain query guidelines

Per retrieved learnings/coding guidelines, chain queries should return Result types with specific errors. These functions currently throw, which makes composition and partial failure handling harder.

Illustrative refactor for one function:

+import type { Result } from "@torus-network/torus-utils/result";
+import { Ok, Err } from "@torus-network/torus-utils/result";
+type QueryError =
+  | { kind: "rpc"; where: "torus0.minAllowedStake"; cause: unknown }
+  | { kind: "parse"; where: "torus0.minAllowedStake"; cause: unknown };

-export async function queryMinAllowedStake(api: Api): Promise<bigint> {
+export async function queryMinAllowedStake(api: Api): Promise<Result<bigint, QueryError>> {
   const [queryError, q] = await tryAsync(api.query.torus0.minAllowedStake());
   if (queryError !== undefined) {
-    console.error("Error querying minimum allowed stake:", queryError);
-    throw queryError;
+    return Err({ kind: "rpc", where: "torus0.minAllowedStake", cause: queryError });
   }
-
-  const [parseError, minAllowedStake] = trySync(() => sb_balance.parse(q));
-  if (parseError !== undefined) {
-    console.error("Error parsing minimum allowed stake:", parseError);
-    throw parseError;
-  }
-
-  return minAllowedStake;
+  const r = sb_balance.safeParse(q);
+  if (!r.success) {
+    return Err({ kind: "parse", where: "torus0.minAllowedStake", cause: r.error });
+  }
+  return Ok(r.data);
 }

If helpful, I can batch-generate analogous diffs for the other query* functions.

Also applies to: 52-96, 102-116, 118-132, 134-150, 152-196, 198-240, 242-282, 284-298, 300-343, 345-389

apps/torus-portal/src/app/(pages)/constraints/_components/constraint-nodes/constraint-node-base.tsx (1)

117-139: Fix BigInt conversion crash for decimal percentages

step="0.1" allows decimals, but BigInt("12.5") throws. Either accept only integers or scale decimals before BigInt.

Minimal integer-only fix:

-  // Validate percentage range
-  const numValue = parseFloat(value);
-  if (value !== "" && (isNaN(numValue) || numValue < 0 || numValue > 100)) {
+  // Validate 0..100 integer
+  const numValue = value === "" ? NaN : Number(value);
+  if (
+    value !== "" &&
+    (!Number.isFinite(numValue) || numValue % 1 !== 0 || numValue < 0 || numValue > 100)
+  ) {
     setPercentageError("Percentage must be between 0 and 100");
     return;
   }
...
-        expression: {
-          ...data.expression,
-          percentage: BigInt(value || "0"),
-        } as BaseConstraintType,
+        expression: {
+          ...data.expression,
+          percentage: BigInt(String(numValue || 0)),
+        } as BaseConstraintType,

And constrain the input:

-            step="0.1"
+            step="1"

If you prefer decimals, we can switch to basis points (scale by 100) and store percentageBp: bigint instead.

Also applies to: 216-226

packages/torus-sdk-ts/src/metadata/metadata.ts (1)

40-49: Harden fetch/JSON error handling and HTTP status checks

Network/parse failures currently throw. Return an Err with context instead.

-export async function processMetadata<T extends CustomMetadata>(
+export async function processMetadata<T extends CustomMetadata>(
   zodSchema: ZodSchema<T>,
   url: string,
   entryId: number,
   kind?: string,
 ): Promise<OldResult<T, CustomDataError>> {
-  const response = await fetch(url);
-  const obj: unknown = await response.json();
+  try {
+    const response = await fetch(url);
+    if (!response.ok) {
+      return { Err: { message: `HTTP ${response.status} for ${kind} ${entryId} at ${url}` } };
+    }
+    const obj: unknown = await response.json();
 
   const validated = zodSchema.safeParse(obj);
   if (!validated.success) {
     const message = `Invalid metadata for ${kind} ${entryId} at ${url}`;
     return { Err: { message } };
   }
   return { Ok: validated.data };
+  } catch (e) {
+    return { Err: { message: `Fetch/parse failed for ${kind} ${entryId} at ${url}: ${String(e)}` } };
+  }
 }
apps/torus-bridge/src/app/_components/transfer-evm.tsx (1)

340-351: Avoid BigInt→Number conversion when computing Max; precision loss risk

Number(BigInt) can overflow and lose precision for typical 18d balances. Use bigint math + formatUnits for a safe decimal string.

   if (mode === "bridge") {
     let maxBalance = userAccountFreeBalance();
     if (maxBalance !== null) {
-        maxBalance = maxBalance - 1n * BigInt(1e18);
-        const maxBalanceString = (Number(maxBalance) / 1e18).toFixed(18);
+        const FEE_BUFFER = 10n ** 18n; // 1 TORUS
+        maxBalance = maxBalance - FEE_BUFFER;
+        const maxBalanceString = formatUnits(maxBalance, 18);
         setAmount(maxBalanceString.replace(/\.?0+$/, ""));
     }
   } else {
     if (torusEvmBalance?.value) {
-        const paddedAmount = torusEvmBalance.value - 1n * BigInt(1e16);
-        const maxBalanceString = (Number(paddedAmount) / 1e18).toFixed(18);
+        const GAS_BUFFER = 10n ** 16n; // 0.01 TORUS
+        const paddedAmount = torusEvmBalance.value - GAS_BUFFER;
+        const maxBalanceString = formatUnits(paddedAmount, 18);
         setAmount(maxBalanceString.replace(/\.?0+$/, ""));
     }
   }

Add import:

import { formatUnits } from "viem";
README.md (2)

3-3: Typo: “TypesSript” → “TypeScript”

User-facing typo in the first paragraph.

-Monorepo for the **Torus Network** TypesSript Ecosystem. It's managed with
+Monorepo for the **Torus Network** TypeScript Ecosystem. It's managed with

84-89: Broken command: just db-push recipe doesn’t exist

Replace with existing DB flow (db-dev-up then db-apply) to match the Justfile.

-# Push the Drizzle schema to the database (required for allocator and governance)
-just db-push
+# Start a local DB and apply migrations (required for allocator and governance)
+just db-dev-up
+just db-apply
apps/torus-bridge/package.json (1)

4-4: Make the app private to prevent accidental publish

Next.js apps shouldn’t be published to npm. Mark the package as private.

-  "private": false,
+  "private": true,
packages/torus-sdk-ts/src/chain/governance/governance-types.ts (1)

107-123: Emission invariants: enforce percentage consistency.

Add a refine to ensure the three percentages sum to 100 (or whatever invariant the runtime enforces).

 export const PROPOSAL_DATA_SCHEMA = sb_enum({
@@
-  Emission: sb_struct({
-    recyclingPercentage: sb_percent,
-    treasuryPercentage: sb_percent,
-    incentivesRatio: sb_percent,
-  }),
+  Emission: sb_struct({
+    recyclingPercentage: sb_percent,
+    treasuryPercentage: sb_percent,
+    incentivesRatio: sb_percent,
+  }).refine(
+    ({ recyclingPercentage, treasuryPercentage, incentivesRatio }) =>
+      recyclingPercentage + treasuryPercentage + incentivesRatio === 100,
+    "Emission percentages must sum to 100",
+  ),
packages/torus-sdk-ts/src/chain/emission0/emission0-storage.ts (1)

6-17: Standardize query functions to return Result<T,E> instead of throwing.

These three functions throw on errors; per chain guidelines, return Result with specific error types (e.g., SbQueryError) and keep tryAsync/trySync. This avoids exception-driven flow and aligns callers across modules.

Apply a representative refactor (adapt error type to your SbQueryError):

-import type { Percent } from "@polkadot/types/interfaces";
-import { tryAsync, trySync } from "@torus-network/torus-utils/try-catch";
+import type { Percent } from "@polkadot/types/interfaces";
+import { tryAsync, trySync } from "@torus-network/torus-utils/try-catch";
+import type { Result } from "@torus-network/torus-utils/result";
+import { makeOk, makeErr } from "@torus-network/torus-utils/result";
+// import type { SbQueryError } from "../common/errors.js";

-export async function queryRecyclingPercentage(api: Api): Promise<Percent> {
+export async function queryRecyclingPercentage(api: Api): Promise<Result<Percent, Error>> {
   const [queryError, recyclingPercentage] = await tryAsync(
     api.query.emission0.emissionRecyclingPercentage(),
   );
-  if (queryError !== undefined) {
-    console.error("Error querying recycling percentage:", queryError);
-    throw queryError;
-  }
-  return recyclingPercentage;
+  if (queryError) return makeErr(queryError);
+  return makeOk(recyclingPercentage);
 }
 
-export async function queryIncentivesRatio(api: Api): Promise<Percent> {
+export async function queryIncentivesRatio(api: Api): Promise<Result<Percent, Error>> {
   const [queryError, incentivesRatio] = await tryAsync(
     api.query.emission0.incentivesRatio(),
   );
-  if (queryError !== undefined) {
-    console.error("Error querying incentives ratio:", queryError);
-    throw queryError;
-  }
-  return incentivesRatio;
+  if (queryError) return makeErr(queryError);
+  return makeOk(incentivesRatio);
 }
 
-export function queryBlockEmission(api: Api): bigint {
+export function queryBlockEmission(api: Api): Result<bigint, Error> {
   const [queryError, q] = trySync(() => api.consts.emission0.blockEmission);
-  if (queryError !== undefined) {
-    console.error("Error querying block emission:", queryError);
-    throw queryError;
-  }
+  if (queryError) return makeErr(queryError);
 
   const [parseError, emission] = trySync(() => sb_bigint.parse(q));
-  if (parseError !== undefined) {
-    console.error("Error parsing block emission:", parseError);
-    throw parseError;
-  }
-  return emission;
+  if (parseError) return makeErr(parseError);
+  return makeOk(emission);
 }

Also applies to: 19-29, 31-45

packages/torus-sdk-ts/src/chain/common/delegation-tree-builder.ts (3)

606-608: Descendant check uses '-' instead of '.' — permissions targeting will fail.

Namespace IDs are dot-separated throughout; using a hyphen prevents matching descendants.

Apply:

-      if (targetNodeId === node.id || targetNodeId.startsWith(node.id + "-")) {
+      if (targetNodeId === node.id || targetNodeId.startsWith(`${node.id}.`)) {
         candidates.push(node);
       }

243-286: Root-owner tracing loop never ascends; it returns after first parent.

currentPermId is never updated to a grandparent ID; the loop doesn’t actually traverse ancestry, potentially misattributing root owners.

Apply a minimal fix to ascend until a None parent is found:

-          Some: async (permId: PermissionId) => {
-            const currentPermId = permId;
-            while (true) {
+          Some: async (permId: PermissionId) => {
+            let currentPermId: PermissionId | null = permId;
+            // Walk up until we find a None (root) entry
+            while (currentPermId !== null) {
               const [error, parentPerm] = await queryPermission(
                 api,
                 currentPermId,
               );
               if (error !== undefined || parentPerm === null) {
                 return makeErr(
                   `Failed to query parent permission ${currentPermId}`,
                 );
               }
 
               if (!("Namespace" in parentPerm.scope)) {
                 return makeErr(
                   `Parent permission ${currentPermId} is not a namespace permission`,
                 );
               }
 
               for (const [grandParentId, paths] of parentPerm.scope.Namespace
                 .paths) {
-                const shouldReturn = match(grandParentId)({
+                const shouldReturn = match(grandParentId)({
                   None() {
                     // Found root - paths with None parent mean delegator owns them
                     return paths.length > 0;
                   },
-                  Some(_id: PermissionId) {
-                    return false;
+                  Some(nextId: PermissionId) {
+                    // keep walking
+                    currentPermId = nextId;
+                    return false;
                   },
                 });
 
                 if (shouldReturn) {
                   return makeOk(parentPerm.delegator);
                 }
               }
-              // No more parents found, current permission's delegator is root
-              return makeOk(parentPerm.delegator);
+              // If we didn't early-return and didn't set a next parent, treat this as root
+              return makeOk(parentPerm.delegator);
             }
           },

382-400: Do not throw inside a Result-returning function.

create() returns Result<...> but throws on malformed edge IDs and parent-child map inconsistencies, breaking the contract.

Refactor to construct edges imperatively and return makeErr on failure:

-    manager.edges = Array.from(edgeSet).map((edgeId) => {
-      const parts = edgeId.split("->");
-      const source = parts[0];
-      const target = parts[1];
-
-      if (source === undefined || target === undefined) {
-        console.warn(`Invalid edge ID format: ${edgeId}`);
-        throw new Error(
-          `Edge ID must be in format "source->target", got: ${edgeId}`,
-        );
-      }
-
-      return {
-        id: edgeId,
-        source,
-        target,
-      };
-    });
+    {
+      const edgesArr: DelegationEdge[] = [];
+      for (const edgeId of edgeSet) {
+        const [source, target] = edgeId.split("->");
+        if (!source || !target) {
+          console.warn(`Invalid edge ID format: ${edgeId}`);
+          return makeErr(new Error(`Invalid edge ID: ${edgeId}`));
+        }
+        edgesArr.push({ id: edgeId, source, target });
+      }
+      manager.edges = edgesArr;
+    }
 
     // Build parent-child relationships
     for (const edge of manager.edges) {
       if (!manager.parentChildMap.has(edge.source)) {
         manager.parentChildMap.set(edge.source, new Set());
       }
       const childrenSet = manager.parentChildMap.get(edge.source);
-      if (childrenSet === undefined) {
-        console.warn(
-          `Parent-child map inconsistency for source: ${edge.source}`,
-        );
-        throw new Error(
-          `Expected children set to exist for source: ${edge.source}`,
-        );
-      }
+      if (childrenSet === undefined) {
+        console.warn(`Parent-child map inconsistency for source: ${edge.source}`);
+        return makeErr(new Error(`Expected children set for source: ${edge.source}`));
+      }
       childrenSet.add(edge.target);
       manager.childParentMap.set(edge.target, edge.source);
     }
 
-    return makeOk(manager);
+    return makeOk(manager);

If you have SbQueryError, replace new Error(...) with a concrete error variant.

Also applies to: 401-417

packages/torus-sdk-ts/src/agent/agent.ts (1)

366-372: Permissions cache isn’t cleared before reload; revoked/expired entries may linger.

loadDelegatedNamespacePermissions() only appends. Clear the map before rebuilding to reflect revocations/expirations after event-driven refresh.

 private async loadDelegatedNamespacePermissions() {
   try {
     console.log(
       `Loading delegated namespace permissions for agent key: ${this.options.agentKey}`,
     );
+    // Rebuild from scratch to avoid stale cache after revocations/expirations
+    this.delegatedNamespacePermissions.clear();

Also applies to: 381-447

packages/torus-sdk-ts/src/chain/rpc.ts (1)

20-57: Return Result<T,E> for queryLastBlock instead of throwing.

Brings this utility in line with chain module conventions and avoids exceptions.

-export async function queryLastBlock(api: ApiPromise): Promise<LastBlock> {
+export async function queryLastBlock(api: ApiPromise): Promise<Result<LastBlock, Error>> {
   const [headerError, blockHeader] = await tryAsync(api.rpc.chain.getHeader());
-  if (headerError !== undefined) {
-    console.error("Error getting block header:", headerError);
-    throw headerError;
-  }
+  if (headerError) return makeErr(headerError);
 
   const [apiError, apiAtBlock] = await tryAsync(api.at(blockHeader.hash));
-  if (apiError !== undefined) {
-    console.error("Error getting API at block:", apiError);
-    throw apiError;
-  }
+  if (apiError) return makeErr(apiError);
 
   const [parseError, blockNumber] = trySync(() =>
     sb_blocks.parse(blockHeader.number),
   );
-  if (parseError !== undefined) {
-    console.error("Error parsing block number:", parseError);
-    throw parseError;
-  }
+  if (parseError) return makeErr(parseError);
 
   const [hashError, blockHashHex] = trySync(() => blockHeader.hash.toHex());
-  if (hashError !== undefined) {
-    console.error("Error converting block hash to hex:", hashError);
-    throw hashError;
-  }
+  if (hashError) return makeErr(hashError);
 
   const lastBlock = {
     blockHeader,
     blockNumber,
     blockHash: blockHeader.hash,
     blockHashHex,
     apiAtBlock,
   };
 
-  return lastBlock;
+  return makeOk(lastBlock);
 }
apps/torus-portal/src/app/(pages)/constraints/_components/constraint-nodes/constraint-node-permission-id.tsx (1)

115-119: Extraneous '$' in permission ID label.

Renders literal “$” before the suffix. Remove it.

-                  <SelectItem key={permissionId} value={permissionId}>
-                    {permissionId.slice(0, 6)}...${permissionId.slice(-4)}
-                  </SelectItem>
+                  <SelectItem key={permissionId} value={permissionId}>
+                    {permissionId.slice(0, 6)}...{permissionId.slice(-4)}
+                  </SelectItem>
packages/torus-sdk-ts/src/chain/permission0/permission0-extrinsics.ts (1)

96-101: Fix BTreeMap value codec: use "u16" (not "u32")

Generic type is u16 but the codec string is "u32" — this will encode values incorrectly; change the codec to "u16".
File: packages/torus-sdk-ts/src/chain/permission0/permission0-extrinsics.ts (lines ~96-101)

-  const recipientsMap_ = new BTreeMap<AccountId32, u16>(
+  const recipientsMap_ = new BTreeMap<AccountId32, u16>(
     api.registry,
     "AccountId32",
-    "u32",
+    "u16",
     recipientsMap,
   );
packages/torus-sdk-ts/src/chain/permission0/permission0-storage.ts (6)

44-59: Return null on missing permission; use sb_option + path and keep Result type precise

sb_some will fail on None, but the docstring promises Ok<null>. Also add path info.

Apply:

 export async function queryPermission(
   api: Api,
   permissionId: string,
 ): Promise<
-  Result<Nullable<PermissionContract>, SbQueryError | ZError<unknown>>
+  Result<Nullable<PermissionContract>, SbQueryError | ZError<PermissionContract>>
 > {
   const [queryError, query] = await tryAsync(
     api.query.permission0.permissions(permissionId),
   );
   if (queryError) return makeErr(SbQueryError.from(queryError));

-  const parsed = sb_some(PERMISSION_CONTRACT_SCHEMA).safeParse(query);
-  if (parsed.success === false) return makeErr(parsed.error);
-
-  return makeOk(parsed.data);
+  const parsed = prefixPath(
+    sb_option(PERMISSION_CONTRACT_SCHEMA).safeParse(query),
+    ["storage", "permission0", "permissions", String(permissionId), "<value>"],
+  );
+  if (!parsed.success) return makeErr(parsed.error);
+  const value = match(parsed.data)({
+    None: () => null,
+    Some: (v) => v,
+  });
+  return makeOk(value);
 }

107-120: Use PermissionId[] consistently

Return type is currently `0x${string}`[] but you parse with PERMISSION_ID_SCHEMA. Align the type.

-export async function queryPermissionsByDelegator(
+export async function queryPermissionsByDelegator(
   api: Api,
   delegator: SS58Address,
-): Promise<Result<`0x${string}`[], SbQueryError | ZError<unknown>>> {
+): Promise<Result<PermissionId[], SbQueryError | ZError<unknown>>> {

340-347: Error union doesn’t match actual parse sites

This function produces Zod errors for address, streamId, permissionId and balance, not ZError<AccumulatedStreamEntry>. Tighten the error type.

 export async function queryAllAccumulatedStreamAmounts(
   api: Api,
 ): Promise<
   Result<
     AccumulatedStreamEntry[],
-    SbQueryError | ZError<AccumulatedStreamEntry>
+    SbQueryError | ZError<SS58Address> | ZError<StreamId> | ZError<PermissionId> | ZError<bigint>
   >
 > {

407-444: Standardize errors, add path info, avoid generic Error

Return SbQueryError | ZError<bigint> and prefix parse errors. Also convert query error via SbQueryError.from.

 export async function queryAccumulatedStreamAmount(
   api: Api,
   delegator: SS58Address,
   streamId: StreamId,
   permissionId: PermissionId,
-): Promise<Result<bigint, Error>> {
+): Promise<Result<bigint, SbQueryError | ZError<bigint>>> {
   const [queryError, queryResult] = await tryAsync(
     api.query.permission0.accumulatedStreamAmounts(
       delegator,
       streamId,
       permissionId,
     ),
   );

-  if (queryError) {
-    return makeErr(
-      new Error(
-        `Failed to query accumulated stream amount: ${queryError.message}`,
-      ),
-    );
-  }
+  if (queryError) return makeErr(SbQueryError.from(queryError));

-  const parsed = sb_option(sb_balance).safeParse(queryResult);
+  const parsed = prefixPath(
+    sb_option(sb_balance).safeParse(queryResult),
+    ["storage", "permission0", "accumulatedStreamAmounts", String(delegator), String(streamId), String(permissionId)],
+  );
   if (!parsed.success) {
-    return makeErr(
-      new Error(`Failed to parse accumulated amount: ${parsed.error.message}`),
-    );
+    return makeErr(parsed.error);
   }

557-570: isPermissionExpired always returns false

You ignore the match result and return false. Return the match value directly.

 export function isPermissionExpired(
   permission: PermissionContract,
   currentBlock: bigint,
 ): boolean {
-  match(permission.duration)({
-    Indefinite() {
-      return false;
-    },
-    UntilBlock(block) {
-      return currentBlock > block;
-    },
-  });
-  return false;
+  return match(permission.duration)({
+    Indefinite: () => false,
+    UntilBlock: (block) => currentBlock > block,
+  });
 }

612-648: Align error types and remove generic Error in isPermissionEnabled

This function can emit SbQueryError from queryPermission. Prefer a domain error for “not found” (or at least keep Error last).

 export async function isPermissionEnabled(
   api: Api,
   permissionId: PermissionId,
-): Promise<Result<boolean, ZError<PermissionContract> | Error>> {
+): Promise<Result<boolean, SbQueryError | ZError<PermissionContract> | Error>> {
   const [permissionError, permission] = await queryPermission(
     api,
     permissionId,
   );
-  if (permissionError !== undefined) return makeErr(permissionError);
+  if (permissionError !== undefined) return makeErr(permissionError);

Optionally introduce a specific NotFoundError to replace the bare Error.

@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify route types generation path

Ensure the Next config actually generates .next/types/routes.d.ts (path/name can differ across Next versions), otherwise TS will fail in a clean workspace.


🏁 Script executed:

#!/bin/bash
# Check config and presence of the generated file name used here.
rg -n "typedRoutes|route.*types" apps/torus-bridge/next.config.mjs || true
rg -n "\.next/types/routes\.d\.ts" -g "!**/node_modules/**" || true

Length of output: 148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Git root: $(git rev-parse --show-toplevel 2>/dev/null || echo 'no git repo')"
echo "---- Top-level files ----"
ls -la | sed -n '1,200p' || true

echo
echo "---- Locate next.config.* files ----"
rg -n --hidden -S --no-ignore-vcs --no-ignore 'next\.config' || true

echo
echo "---- Check apps/torus-bridge presence ----"
if [ -d "apps/torus-bridge" ]; then
  echo "apps/torus-bridge exists"
  ls -la apps/torus-bridge | sed -n '1,200p' || true
else
  echo "apps/torus-bridge missing"
fi

echo
echo "---- Show apps/torus-bridge/next-env.d.ts if present ----"
if [ -f "apps/torus-bridge/next-env.d.ts" ]; then
  echo "FOUND: apps/torus-bridge/next-env.d.ts"
  sed -n '1,200p' apps/torus-bridge/next-env.d.ts
else
  echo "NOT FOUND: apps/torus-bridge/next-env.d.ts"
fi

echo
echo "---- Search repository for references to routes.d.ts or .next/types/routes.d.ts ----"
rg -n --hidden -S --no-ignore-vcs --no-ignore '\\.next/types/routes\\.d\\.ts' || true
rg -n --hidden -S --no-ignore-vcs --no-ignore 'routes\\.d\\.ts' || true

echo
echo "---- Search for plugins or patterns that generate typed routes ----"
rg -n --hidden -S --no-ignore-vcs --no-ignore 'typedRoutes|typed-routes|next-typed-routes|next-typed|route-types' || true

echo
echo "---- Find any .next directories and show matching files ----"
find . -type f -path '*/.next/*' -name '*routes*.d.ts' -print -exec bash -lc 'echo "---- {} ----"; sed -n "1,200p" {}' \; 2>/dev/null || true

echo
echo "---- Completed search ----"

Length of output: 4437


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "---- apps/torus-bridge/next.config.mjs ----"
if [ -f "apps/torus-bridge/next.config.mjs" ]; then
  sed -n '1,400p' apps/torus-bridge/next.config.mjs
else
  echo "MISSING"
fi

echo
echo "---- apps/torus-bridge/package.json ----"
if [ -f "apps/torus-bridge/package.json" ]; then
  sed -n '1,400p' apps/torus-bridge/package.json
else
  echo "MISSING"
fi

echo
echo "---- apps/torus-bridge/tsconfig.json ----"
if [ -f "apps/torus-bridge/tsconfig.json" ]; then
  sed -n '1,400p' apps/torus-bridge/tsconfig.json
else
  echo "MISSING"
fi

echo
echo "---- Repo search for route-type generators / references ----"
rg -n --hidden -S --no-ignore-vcs --no-ignore 'routes\.d\.ts|\.next/types/routes|typedRoutes|typed-routes|route-types|next-typed|nextjs-typed|@types/routes' || true

echo
echo "---- Search package.json scripts for generate/build step mentioning routes/types ----"
rg -n --hidden -S --no-ignore-vcs --no-ignore '"(generate|routes|typed|types?)"' package.json apps/**/package.json || true

Length of output: 5001


Verify route types generation path

apps/torus-bridge/next-env.d.ts:3 (also apps/torus-portal/next-env.d.ts:3, apps/torus-page/next-env.d.ts:3) references "./.next/types/routes.d.ts" but there is no generator or build step in this repo that emits that file — a clean clone will break TypeScript. Either enable/add a codegen/build step to produce .next/types/routes.d.ts, commit a stable declaration, or remove/update the triple-slash reference and tsconfig includes so TS does not depend on .next/types/routes.d.ts.

🤖 Prompt for AI Agents
In apps/torus-bridge/next-env.d.ts (and similarly
apps/torus-portal/next-env.d.ts, apps/torus-page/next-env.d.ts) around line 3
the triple-slash reference "./.next/types/routes.d.ts" points to a file that is
not generated in this repo and breaks a clean TypeScript build; fix by either
adding a deterministic generation step that emits .next/types/routes.d.ts during
install/build and wire it into package.json/build scripts, or commit a stable
declaration file into source control at that path, or remove/update the
triple-slash reference and any tsconfig include entries so TS no longer depends
on .next/types/routes.d.ts (e.g., delete the reference line and adjust tsconfig
to not include .next/types or replace with a correct path to an existing
declaration).

Comment on lines +46 to +65
// Optimize bundle splitting
if (!isServer) {
config.optimization.splitChunks = {
chunks: "all",
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
priority: 10,
},
wallet: {
test: /[\\/]node_modules[\\/](@solana|@cosmos-kit|@starknet|@hyperlane)[\\/]/,
name: "wallet-vendors",
chunks: "all",
priority: 20,
},
},
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid overriding splitChunks; let Next handle vendor chunking.

Custom optimization.splitChunks can fight Next’s defaults and hurt caching. Prefer route-level code splitting and experimental.optimizePackageImports you already enabled.

-    if (!isServer) {
-      config.optimization.splitChunks = {
-        chunks: "all",
-        cacheGroups: {
-          vendor: {
-            test: /[\\/]node_modules[\\/]/,
-            name: "vendors",
-            chunks: "all",
-            priority: 10,
-          },
-          wallet: {
-            test: /[\\/]node_modules[\\/](@solana|@cosmos-kit|@starknet|@hyperlane)[\\/]/,
-            name: "wallet-vendors",
-            chunks: "all",
-            priority: 20,
-          },
-        },
-      };
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Optimize bundle splitting
if (!isServer) {
config.optimization.splitChunks = {
chunks: "all",
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
priority: 10,
},
wallet: {
test: /[\\/]node_modules[\\/](@solana|@cosmos-kit|@starknet|@hyperlane)[\\/]/,
name: "wallet-vendors",
chunks: "all",
priority: 20,
},
},
};
}
🤖 Prompt for AI Agents
In apps/torus-bridge/next.config.mjs around lines 46 to 65, the custom
server-side config.optimization.splitChunks override is replacing Next.js'
built-in vendor splitting and harming caching; remove this entire conditional
block that sets config.optimization.splitChunks so Next can manage vendor
chunking itself, and rely on route-level code splitting and the already-enabled
experimental.optimizePackageImports for package-level optimization instead.

Comment on lines 16 to 46
"dependencies": {
"@cosmos-kit/cosmostation": "^2.14.1",
"@cosmos-kit/keplr": "^2.14.2",
"@cosmos-kit/leap": "^2.14.2",
"@hyperlane-xyz/registry": "^6.10.0",
"@hyperlane-xyz/sdk": "^7.3.0",
"@hyperlane-xyz/utils": "^7.3.0",
"@hyperlane-xyz/widgets": "^7.3.0",
"@solana/wallet-adapter-base": "^0.9.24",
"@solana/wallet-adapter-react": "^0.15.36",
"@solana/wallet-adapter-react-ui": "^0.9.36",
"@solana/wallet-adapter-wallets": "^0.19.33",
"@solana/web3.js": "^1.95.4",
"@cosmos-kit/cosmostation": "^2.15.3",
"@cosmos-kit/keplr": "^2.15.3",
"@cosmos-kit/leap": "^2.15.3",
"@hyperlane-xyz/registry": "^23.1.0",
"@hyperlane-xyz/sdk": "^17.0.0",
"@hyperlane-xyz/utils": "^17.0.0",
"@hyperlane-xyz/widgets": "^17.0.0",
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.39",
"@solana/wallet-adapter-wallets": "^0.19.37",
"@solana/web3.js": "^1.98.4",
"@starknet-react/chains": "^4.0.4",
"@starknet-react/core": "^3.7.2",
"@tanstack/react-query": "catalog:",
"@torus-network/sdk": "workspace:*",
"@torus-ts/env-validation": "workspace:*",
"@torus-ts/query-provider": "workspace:*",
"@torus-ts/torus-provider": "workspace:*",
"@torus-network/torus-utils": "workspace:*",
"@torus-ts/ui": "workspace:*",
"formik": "^2.4.6",
"lucide-react": "catalog:",
"next": "catalog:",
"react": "catalog:react19",
"react-dom": "catalog:react19",
"starknet": "^6.24.1",
"yaml-loader": "^0.8.1",
"zod": "catalog:"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify Next.js transpile settings for Starknet/Wallet adapters

Starknet and some Solana adapters may require transpilation. Ensure next.config.mjs has transpilePackages for the newly added libs.

Run:

Expected: includes entries like "starknet", "@starknet-react/core", possibly "@solana/wallet-adapter-.*".


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'transpilePackages\s*:\s*\[' apps/torus-bridge/next.config.mjs

Length of output: 84


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="apps/torus-bridge/next.config.mjs"
if [ ! -f "$file" ]; then
  echo "MISSING: $file"
  exit 0
fi
echo "== File: $file =="
nl -ba "$file" | sed -n '1,240p'
echo "== grep transpilePackages context =="
rg -nP -C200 "transpilePackages\s*:\s*\[" "$file" || true
start_line=$(rg -n "transpilePackages\s*:\s*\[" "$file" | cut -d: -f2 || true)
if [ -n "$start_line" ]; then
  end_line=$((start_line + 200))
  echo "== transpilePackages block (lines ${start_line}-${end_line}) =="
  nl -ba "$file" | sed -n "${start_line},${end_line}p"
fi

Length of output: 86


Add Starknet & Solana wallet-adapter packages to transpilePackages (apps/torus-bridge/next.config.mjs)

transpilePackages currently only lists @torus-ts packages (starts ~line 28); add the packages below to avoid Next.js transpile/runtime issues:

  • "starknet"
  • "@starknet-react/core"
  • "@starknet-react/chains"
  • "@solana/wallet-adapter-base"
  • "@solana/wallet-adapter-react"
  • "@solana/wallet-adapter-react-ui"
  • "@solana/wallet-adapter-wallets"
🤖 Prompt for AI Agents
In apps/torus-bridge/next.config.mjs around where transpilePackages is defined
(starts ~line 28), the list currently only contains @torus-ts packages; add the
listed Starknet and Solana wallet-adapter packages to that array to ensure
Next.js transpiles them: "starknet", "@starknet-react/core",
"@starknet-react/chains", "@solana/wallet-adapter-base",
"@solana/wallet-adapter-react", "@solana/wallet-adapter-react-ui",
"@solana/wallet-adapter-wallets".

Comment on lines +33 to +41
<ProviderRenderer
providers={[
ReactQueryProvider,
WarpContextInitGateProvider,
EvmWalletProvider,
SolanaWalletProvider,
StarknetWalletProvider,
CosmosWalletProvider,
]}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Critical: Cosmos wallet metadata contains harmful placeholder text

CosmosWalletProvider (see its file) sets WalletConnect metadata name: "suicide", description: "i want", url: "http://localhost:3006/". This is user-facing and unacceptable for production.

Please sanitize immediately (example below in the provider file):

-          metadata: {
-            name: "suicide",
-            description: "i want",
-            url: "http://localhost:3006/",
-            icons: [],
-          },
+          metadata: {
+            name: "Torus Bridge",
+            description: "Bridge assets across supported chains.",
+            url: "https://bridge.torus.network",
+            icons: ["https://bridge.torus.network/icon.png"],
+          },

Also ensure this provider is not included in production builds until metadata is correct.

Search and fix all occurrences:


🏁 Script executed:

#!/bin/bash
rg -nP --pretty 'name:\s*"suicide"|description:\s*"i want"|localhost:3006' apps/torus-bridge/src

Length of output: 172


Critical: Replace offensive WalletConnect metadata in CosmosWalletProvider (apps/torus-bridge/src/context/cosmos-wallet-provider.tsx:75-77)
CosmosWalletProvider currently sets user-facing metadata to name: "suicide", description: "i want", url: "http://localhost:3006/" — unacceptable for production. Replace immediately with correct product metadata and exclude this provider from production builds until fixed.

-          metadata: {
-            name: "suicide",
-            description: "i want",
-            url: "http://localhost:3006/",
-            icons: [],
-          },
+          metadata: {
+            name: "Torus Bridge",
+            description: "Bridge assets across supported chains.",
+            url: "https://bridge.torus.network",
+            icons: ["https://bridge.torus.network/icon.png"],
+          },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/torus-bridge/src/context/cosmos-wallet-provider.tsx around lines 75-77,
the CosmosWalletProvider sets offensive user-facing WalletConnect metadata;
replace those fields with correct product metadata (e.g., name: "Torus Bridge",
description: "Torus Bridge Wallet Connector", url: use the official product URL
or an environment variable like process.env.PRODUCT_URL) and ensure
sensitive/placeholder text is removed. Then in
apps/torus-bridge/src/context/app-context-provider.tsx (lines ~33-41) guard
inclusion of CosmosWalletProvider so it is excluded from production builds until
verified—wrap its entry behind a runtime check (NODE_ENV !== 'production' or a
dedicated feature flag/env var) so the provider is not included in production
bundles.

Comment on lines +187 to 196
walletPermissions: Map<string, PermissionContract>;
}): Set<string> {
const allAddresses = new Set<string>();

[
...permissions.streamPermissions,
...permissions.namespacePermissions,
...permissions.curatorPermissions,
...permissions.walletPermissions,
].forEach(([_, contract]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify call sites updated for new walletPermissions parameter.

Both extractAllAddressesFromPermissions and sortPermissionsByRole now require walletPermissions. Ensure all callers pass it to avoid runtime errors.

Also applies to: 232-240, 278-279


🏁 Script executed:

#!/bin/bash
# List call sites and review argument objects include walletPermissions
rg -n --type=ts 'extractAllAddressesFromPermissions\(' -C2
rg -n --type=ts 'sortPermissionsByRole\(' -C2

Length of output: 2601


Fix call sites to pass new walletPermissions

Both helpers now require walletPermissions; the callers were not updated — pass the walletPermissions Map (or a permissions object that includes it) to avoid runtime errors.

  • apps/torus-portal/src/app/_components/permission-selector/permission-selector.tsx: line 81 — update extractAllAddressesFromPermissions(permissions) to pass walletPermissions.
  • apps/torus-portal/src/app/_components/permission-selector/permission-selector.tsx: line 100 — update sortPermissionsByRole(permissions, userAddress) to pass walletPermissions.
🤖 Prompt for AI Agents
In
apps/torus-portal/src/app/_components/permission-selector/permission-selector.utils.ts
around lines 187 to 196, several helper functions now require the new
walletPermissions parameter but call sites were not updated; update the two call
sites in
apps/torus-portal/src/app/_components/permission-selector/permission-selector.ts:
at line 81 change extractAllAddressesFromPermissions(permissions) to pass the
walletPermissions Map (e.g., extractAllAddressesFromPermissions(permissions,
permissions.walletPermissions) or supply the permissions object that includes
walletPermissions), and at line 100 change sortPermissionsByRole(permissions,
userAddress) to include the walletPermissions argument as well (e.g.,
sortPermissionsByRole(permissions, userAddress, permissions.walletPermissions)),
ensuring the Map is forwarded to avoid runtime errors.

Comment on lines +1 to +2
import { bool, Int, Null, UInt } from "@polkadot/types";
import { AbstractInt } from "@polkadot/types-codec/abstract";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix imports and instanceof checks for Polkadot codec types; correct z.custom usage.

bool is a type, not a constructor; instanceof bool will fail. Import Bool/Int/UInt/Null from @polkadot/types-codec and use them in instanceof. Also, z.custom’s second arg must be params, not a raw string.

Apply this diff:

- import { bool, Int, Null, UInt } from "@polkadot/types";
- import { AbstractInt } from "@polkadot/types-codec/abstract";
+ import { Bool, Int, UInt, Null } from "@polkadot/types-codec";
+ import { AbstractInt } from "@polkadot/types-codec/abstract";

-export const Null_schema = z.custom<Null>(
-  (val) => val instanceof Null,
-  "not a Substrate Null",
-);
+export const Null_schema = z.custom<Null>(
+  (val) => val instanceof Null,
+  { message: "not a Substrate Null" },
+);

-export const bool_schema = z.custom<bool>(
-  (val) => val instanceof bool,
-  "not a Substrate bool",
-);
+export const bool_schema = z.custom<Bool>(
+  (val) => val instanceof Bool,
+  { message: "not a Substrate bool" },
+);

-export const AbstractInt_schema = z.custom<AbstractInt>(
-  (val) => val instanceof AbstractInt,
-  "not a Substrate AbstractInt",
-);
+export const AbstractInt_schema = z.custom<AbstractInt>(
+  (val) => val instanceof AbstractInt,
+  { message: "not a Substrate AbstractInt" },
+);

-export const UInt_schema = z.custom<UInt>(
-  (val) => val instanceof UInt,
-  "not a Substrate UInt",
-);
+export const UInt_schema = z.custom<UInt>(
+  (val) => val instanceof UInt,
+  { message: "not a Substrate UInt" },
+);

-export const Int_schema = z.custom<Int>(
-  (val) => val instanceof Int,
-  "not a Substrate Int",
-);
+export const Int_schema = z.custom<Int>(
+  (val) => val instanceof Int,
+  { message: "not a Substrate Int" },
+);

Also applies to: 26-30, 41-44, 46-50, 51-55

🤖 Prompt for AI Agents
In packages/torus-sdk-ts/src/substrate-parsers/primitives.ts around lines 1-2
(and also apply same change to ranges 26-30, 41-44, 46-50, 51-55): the code
currently imports bool/Int/UInt/Null from @polkadot/types and uses instanceof
against those, and passes a raw string as the second argument to z.custom.
Replace the imports to pull Bool, Int, UInt, Null (and AbstractInt if needed)
from @polkadot/types-codec so instanceof checks work against the codec
constructors, update all instanceof checks to use those classes, and change
z.custom invocations to pass a parameter object (e.g., { something: value }) as
the second argument rather than a raw string per zod API.

Comment on lines +84 to +92
export const sb_percent = sb_number_int.transform((val, ctx) => {
if (val < 0 || val > 100) {
ctx.addIssue({
code: "custom",
message: `Percent must be between 0 and 100`,
});
}
return val;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make percent validator fail-hard when out of bounds.

You add an issue but still return a value. Return z.NEVER to ensure validation fails.

 export const sb_percent = sb_number_int.transform((val, ctx) => {
   if (val < 0 || val > 100) {
     ctx.addIssue({
       code: "custom",
       message: `Percent must be between 0 and 100`,
     });
+    return z.NEVER;
   }
   return val;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const sb_percent = sb_number_int.transform((val, ctx) => {
if (val < 0 || val > 100) {
ctx.addIssue({
code: "custom",
message: `Percent must be between 0 and 100`,
});
}
return val;
});
export const sb_percent = sb_number_int.transform((val, ctx) => {
if (val < 0 || val > 100) {
ctx.addIssue({
code: "custom",
message: `Percent must be between 0 and 100`,
});
return z.NEVER;
}
return val;
});
🤖 Prompt for AI Agents
In packages/torus-sdk-ts/src/substrate-parsers/primitives.ts around lines 84 to
92, the percent transform currently adds an issue but still returns the value
when out of bounds; change it to return z.NEVER after calling ctx.addIssue so
the validation fails-hard, and ensure z (from 'zod') is imported/available in
this module.

Comment on lines +19 to +44
export const z_typed_map = <T extends ZodRawShape>(
shape: T,
params?: ZodRawCreateParams,
) =>
z
.custom<Map<unknown, unknown>>((data) => data instanceof Map, "not a Map")
.transform((map, ctx) => {
const obj: Record<string | number | symbol, unknown> = {};
for (const [key, value] of map.entries()) {
if (
typeof key !== "string" &&
typeof key !== "number" &&
typeof key !== "symbol"
) {
ctx.addIssue({
code: "custom",
message: `Map key must be a string, number, or symbol. Received ${typeof key}`,
});
continue;
}
obj[key] = value;
}
return obj;
})
.pipe(z.object(shape, params));

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Restrict keys to string to match z.object shape.

z.object doesn’t accept symbol keys; allowing symbols here is misleading.

-      const obj: Record<string | number | symbol, unknown> = {};
+      const obj: Record<string, unknown> = {};
       for (const [key, value] of map.entries()) {
-        if (
-          typeof key !== "string" &&
-          typeof key !== "number" &&
-          typeof key !== "symbol"
-        ) {
+        if (typeof key !== "string") {
           ctx.addIssue({
             code: "custom",
-            message: `Map key must be a string, number, or symbol. Received ${typeof key}`,
+            message: `Map key must be a string. Received ${typeof key}`,
           });
           continue;
         }
-        obj[key] = value;
+        obj[key] = value;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const z_typed_map = <T extends ZodRawShape>(
shape: T,
params?: ZodRawCreateParams,
) =>
z
.custom<Map<unknown, unknown>>((data) => data instanceof Map, "not a Map")
.transform((map, ctx) => {
const obj: Record<string | number | symbol, unknown> = {};
for (const [key, value] of map.entries()) {
if (
typeof key !== "string" &&
typeof key !== "number" &&
typeof key !== "symbol"
) {
ctx.addIssue({
code: "custom",
message: `Map key must be a string, number, or symbol. Received ${typeof key}`,
});
continue;
}
obj[key] = value;
}
return obj;
})
.pipe(z.object(shape, params));
export const z_typed_map = <T extends ZodRawShape>(
shape: T,
params?: ZodRawCreateParams,
) =>
z
.custom<Map<unknown, unknown>>((data) => data instanceof Map, "not a Map")
.transform((map, ctx) => {
const obj: Record<string, unknown> = {};
for (const [key, value] of map.entries()) {
if (typeof key !== "string") {
ctx.addIssue({
code: "custom",
message: `Map key must be a string. Received ${typeof key}`,
});
continue;
}
obj[key] = value;
}
return obj;
})
.pipe(z.object(shape, params));
🤖 Prompt for AI Agents
In packages/torus-sdk-ts/src/substrate-parsers/struct.ts around lines 19 to 44,
the transform currently allows Map keys of type string|number|symbol but
z.object only supports string keys; change the validation to accept only string
keys (reject number and symbol keys), update the ctx.addIssue message
accordingly, and ensure the accumulated obj is typed as Record<string, unknown>
(use string keys only) so the resulting object matches z.object(shape, params).

Comment on lines +119 to +157
export const sb_struct_obj = <MS extends ZodRawShape, OS extends ZodRawShape>(
mapShape: MS,
objShape: OS,
params?: ZodRawCreateParams,
) =>
Struct_schema.transform((inputValue, ctx) => {
const obj: Record<string, unknown> = {};

// Handle Struct as Map
for (const key of Object.keys(mapShape)) {
const value = inputValue.get(key);
if (value === undefined) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct (as Map)`,
path: [key],
});
return z.NEVER;
}
obj[key] = value;
}

const objKeys = Object.keys(objShape);
for (const key of objKeys) {
const inputObj = inputValue as unknown as Record<string, unknown>;
const val = inputObj[key];
if (val === undefined) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct`,
path: [key],
});
return z.NEVER;
}
obj[key] = val;
}

return obj;
}).pipe(z.object<Merge<MS, OS>>({ ...mapShape, ...objShape }, params));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Respect optional fields in shapes; don’t error on missing optional keys.

If mapShape[key] or objShape[key] is optional, skip the missing-key issue.

-    for (const key of Object.keys(mapShape)) {
+    for (const key of Object.keys(mapShape)) {
       const value = inputValue.get(key);
-      if (value === undefined) {
+      const schema = mapShape[key];
+      const isOptional =
+        (schema as any)?._def?.typeName === z.ZodFirstPartyTypeKind.ZodOptional;
+      if (value === undefined && !isOptional) {
         ctx.addIssue({
           code: "custom",
           message: `Missing key ${key} in Struct (as Map)`,
           path: [key],
         });
         return z.NEVER;
       }
       obj[key] = value;
     }
@@
-    for (const key of objKeys) {
+    for (const key of objKeys) {
       const inputObj = inputValue as unknown as Record<string, unknown>;
       const val = inputObj[key];
-      if (val === undefined) {
+      const schema = objShape[key];
+      const isOptional =
+        (schema as any)?._def?.typeName === z.ZodFirstPartyTypeKind.ZodOptional;
+      if (val === undefined && !isOptional) {
         ctx.addIssue({
           code: "custom",
           message: `Missing key ${key} in Struct`,
           path: [key],
         });
         return z.NEVER;
       }
       obj[key] = val;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const sb_struct_obj = <MS extends ZodRawShape, OS extends ZodRawShape>(
mapShape: MS,
objShape: OS,
params?: ZodRawCreateParams,
) =>
Struct_schema.transform((inputValue, ctx) => {
const obj: Record<string, unknown> = {};
// Handle Struct as Map
for (const key of Object.keys(mapShape)) {
const value = inputValue.get(key);
if (value === undefined) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct (as Map)`,
path: [key],
});
return z.NEVER;
}
obj[key] = value;
}
const objKeys = Object.keys(objShape);
for (const key of objKeys) {
const inputObj = inputValue as unknown as Record<string, unknown>;
const val = inputObj[key];
if (val === undefined) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct`,
path: [key],
});
return z.NEVER;
}
obj[key] = val;
}
return obj;
}).pipe(z.object<Merge<MS, OS>>({ ...mapShape, ...objShape }, params));
export const sb_struct_obj = <MS extends ZodRawShape, OS extends ZodRawShape>(
mapShape: MS,
objShape: OS,
params?: ZodRawCreateParams,
) =>
Struct_schema.transform((inputValue, ctx) => {
const obj: Record<string, unknown> = {};
// Handle Struct as Map
for (const key of Object.keys(mapShape)) {
const value = inputValue.get(key);
const schema = mapShape[key];
const isOptional =
(schema as any)?._def?.typeName === z.ZodFirstPartyTypeKind.ZodOptional;
if (value === undefined && !isOptional) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct (as Map)`,
path: [key],
});
return z.NEVER;
}
obj[key] = value;
}
const objKeys = Object.keys(objShape);
for (const key of objKeys) {
const inputObj = inputValue as unknown as Record<string, unknown>;
const val = inputObj[key];
const schema = objShape[key];
const isOptional =
(schema as any)?._def?.typeName === z.ZodFirstPartyTypeKind.ZodOptional;
if (val === undefined && !isOptional) {
ctx.addIssue({
code: "custom",
message: `Missing key ${key} in Struct`,
path: [key],
});
return z.NEVER;
}
obj[key] = val;
}
return obj;
}).pipe(z.object<Merge<MS, OS>>({ ...mapShape, ...objShape }, params));
🤖 Prompt for AI Agents
In packages/torus-sdk-ts/src/substrate-parsers/struct.ts around lines 119–157,
the transform currently treats any missing key in mapShape or objShape as an
error; update the logic to first detect whether the corresponding Zod schema for
the key is optional and, if so, skip adding a missing-key issue (and leave the
property as undefined or omit it) instead of returning z.NEVER. Concretely: for
each key, inspect the schema in mapShape[key] / objShape[key] to determine
optionality (e.g., ZodOptional wrappers or schema.isOptional if available) and
only add ctx.addIssue and return z.NEVER when the key is missing and the schema
is required; ensure optional keys are allowed to be absent and the constructed
obj reflects undefined/absence appropriately so downstream z.object typing
remains correct.

Comment on lines +20 to +34
export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
(val, ctx) => {
if (val instanceof Text) {
return val.toString();
}
if (!val.isUtf8) {
ctx.addIssue({
code: "custom",
message: `Bytes is not valid UTF8`,
});
return z.NEVER;
}
return val.toUtf8();
},
);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Likely runtime errors: Bytes.isUtf8 / Bytes.toUtf8 may not exist. Use TextDecoder on toU8a().

Replace property/method checks with a strict UTF-8 decode using TextDecoder(fatal: true). Abort with z.NEVER on decode errors.

Apply:

-export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
-  (val, ctx) => {
-    if (val instanceof Text) {
-      return val.toString();
-    }
-    if (!val.isUtf8) {
-      ctx.addIssue({
-        code: "custom",
-        message: `Bytes is not valid UTF8`,
-      });
-      return z.NEVER;
-    }
-    return val.toUtf8();
-  },
-);
+export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
+  (val, ctx) => {
+    if (val instanceof Text) {
+      return val.toString();
+    }
+    try {
+      // Strict decode; throws on invalid UTF-8
+      const decoder = new TextDecoder("utf-8", { fatal: true });
+      return decoder.decode(val.toU8a());
+    } catch {
+      ctx.addIssue({ code: "custom", message: "Bytes is not valid UTF-8" });
+      return z.NEVER;
+    }
+  },
+);

If you prefer utilities, we can switch to @polkadot/util’s u8aToString with strict options if available.


Use a strict UTF‑8 decode on toU8a() instead of Bytes.isUtf8 / toUtf8

Don't rely on Bytes.isUtf8/toUtf8 (they may not exist); strictly decode val.toU8a() with TextDecoder({ fatal: true }) and addIssue + return z.NEVER on failure.

-export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
-  (val, ctx) => {
-    if (val instanceof Text) {
-      return val.toString();
-    }
-    if (!val.isUtf8) {
-      ctx.addIssue({
-        code: "custom",
-        message: `Bytes is not valid UTF8`,
-      });
-      return z.NEVER;
-    }
-    return val.toUtf8();
-  },
-);
+export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
+  (val, ctx) => {
+    if (val instanceof Text) {
+      return val.toString();
+    }
+    try {
+      // Strict decode; throws on invalid UTF-8
+      const decoder = new TextDecoder("utf-8", { fatal: true });
+      return decoder.decode(val.toU8a());
+    } catch {
+      ctx.addIssue({ code: "custom", message: "Bytes is not valid UTF-8" });
+      return z.NEVER;
+    }
+  },
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
(val, ctx) => {
if (val instanceof Text) {
return val.toString();
}
if (!val.isUtf8) {
ctx.addIssue({
code: "custom",
message: `Bytes is not valid UTF8`,
});
return z.NEVER;
}
return val.toUtf8();
},
);
export const sb_string = Text_schema.or(Bytes_schema).transform<string>(
(val, ctx) => {
if (val instanceof Text) {
return val.toString();
}
try {
// Strict decode; throws on invalid UTF-8
const decoder = new TextDecoder("utf-8", { fatal: true });
return decoder.decode(val.toU8a());
} catch {
ctx.addIssue({ code: "custom", message: "Bytes is not valid UTF-8" });
return z.NEVER;
}
},
);
🤖 Prompt for AI Agents
In packages/torus-sdk-ts/src/substrate-parsers/text.ts around lines 20 to 34,
replace the current Bytes.isUtf8 / toUtf8 usage with a strict UTF-8 decode of
val.toU8a(): if val is a Text instance keep returning val.toString(); otherwise
call val.toU8a(), run new TextDecoder("utf-8", { fatal: true }).decode(u8a)
inside a try/catch, on success return the decoded string, on failure call
ctx.addIssue({ code: "custom", message: "Bytes is not valid UTF8" }) and return
z.NEVER; this avoids relying on optional Bytes methods and enforces strict UTF‑8
decoding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants