Skip to content

Conversation

@lissavxo
Copy link
Collaborator

@lissavxo lissavxo commented Dec 23, 2025

Related to #44

Description

Description

This PR adds full support for token-based payments and updates the payment flow accordingly.

Changes included:

  • Accepts a token-id parameter to enable token-specific payments

  • Detects and validates payments for the specified token

  • Fetches the token icon and displays it in the QR code

  • Updates the dialog text to display the token ticker (e.g., Send 10 XECX)

  • Updates the payment URL to include token_id and token_decimalized_qty, removing unused parameters

Will continue the following remaining steps in another PR:

  • Convert the currency amount to the token amount

Test plan

Create a button with token-id param check dialog text, qrcode icon. Copy the payment url check if it contains the token-id, test the payment url in cashtab, make a payment with the specifications of the button and make sure the payment is detected.

Summary by CodeRabbit

  • New Features
    • Token payment support: accept a token identifier so payments can use specific tokens, show token-aware amounts, names and icons, and reflect token-specific real-time updates.
  • Documentation
    • Added token-id usage docs and sidebar entries with HTML, JS, and React examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

📝 Walkthrough

Walkthrough

Adds optional tokenId end-to-end: DOM allowance, new prop on PayButton → PaymentDialog → Widget, token-aware Chronik utilities and websocket plumbing, token-specific URL/QR/donation behavior, and documentation updates for tokenId.

Changes

Cohort / File(s) Summary
Public API / DOM entry
paybutton/src/index.tsx
Added tokenId to allowed DOM attributes so it can be supplied via markup.
Component prop propagation
react/lib/components/PayButton/PayButton.tsx, react/lib/components/PaymentDialog/PaymentDialog.tsx
Added optional tokenId to props/destructuring and forwarded it through dialog and widget setup (including websocket setup payload).
Widget token integration
react/lib/components/Widget/Widget.tsx
Added tokenId prop and tokenName state; fetches token metadata, updates icon/QR, UI text, URL/donation parameters, and branches for token-aware rendering and amount handling.
Chronik utilities
react/lib/util/chronik.ts
Added getTokenAmount() and getTokenInfo(); extended getTransactionFromChronikTransaction(), parseWebsocketMessage(), and websocket initializer signatures to accept tokenId and use token amounts when provided.
Socket plumbing
react/lib/util/socket.ts
Added tokenId?: string to SetupTxsSocketParams and forwarded it into Chronik websocket initialization.
Documentation
docs/README.md, docs/_sidebar.md, docs/zh-cn/README.md, docs/zh-cn/_sidebar.md, docs/zh-tw/README.md, docs/zh-tw/_sidebar.md
New token-id docs and sidebar entries with HTML/JS/React examples.
Manifest
package.json
Listed in manifest (no PR content changes).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PayButton
    participant PaymentDialog
    participant Widget
    participant ChronikUtil
    participant ChronikWS

    User->>PayButton: instantiate (props incl. tokenId?)
    PayButton->>PaymentDialog: open (pass tokenId)
    PaymentDialog->>Widget: render (pass tokenId)

    alt tokenId provided
        Widget->>ChronikUtil: getTokenInfo(tokenId, address)
        ChronikUtil-->>Widget: token metadata
        Widget->>Widget: set tokenName / icon
        Widget->>ChronikWS: setupChronikWebSocket(address, setNewTx, tokenId)
    else no tokenId
        Widget->>ChronikWS: setupChronikWebSocket(address, setNewTx)
    end

    ChronikWS->>ChronikWS: receive websocket msg
    ChronikWS->>ChronikUtil: parseWebsocketMessage(msg, setNewTx, chronik, address, tokenId?)
    alt tokenId present in parse
        ChronikUtil->>ChronikUtil: getTokenAmount(tx, tokenId, address)
        ChronikUtil-->>ChronikWS: token-aware transaction
    else
        ChronikUtil-->>ChronikWS: normal transaction
    end
    ChronikWS-->>Widget: newTx event (token-aware if applicable)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement (behind the scenes)

Suggested reviewers

  • chedieck
  • Klakurka

Poem

🐰 A token hops from prop to light,
From Button down to Widget bright,
I fetch its name and tiny sprite,
QR and links wear token’s sight,
Hop, token, hop — delight tonight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: prop tokenId' is concise and clearly indicates the main feature being added, aligning with the changeset that primarily adds tokenId support across multiple components and documentation.
Description check ✅ Passed The description follows the template structure with Related to, Description, and Test plan sections. It clearly explains the feature, lists changes, acknowledges future work, and provides a concrete test plan.
✨ 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 feat/token-id

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 356e9e8 and 7dc3553.

📒 Files selected for processing (12)
  • docs/README.md
  • docs/_sidebar.md
  • docs/zh-cn/README.md
  • docs/zh-cn/_sidebar.md
  • docs/zh-tw/README.md
  • docs/zh-tw/_sidebar.md
  • paybutton/src/index.tsx
  • react/lib/components/PayButton/PayButton.tsx
  • react/lib/components/PaymentDialog/PaymentDialog.tsx
  • react/lib/components/Widget/Widget.tsx
  • react/lib/util/chronik.ts
  • react/lib/util/socket.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • react/lib/components/PayButton/PayButton.tsx
  • react/lib/components/PaymentDialog/PaymentDialog.tsx
  • docs/zh-cn/README.md
  • paybutton/src/index.tsx
  • react/lib/util/socket.ts
  • docs/zh-cn/_sidebar.md
🧰 Additional context used
🧬 Code graph analysis (2)
react/lib/util/chronik.ts (2)
react/lib/util/types.ts (1)
  • Transaction (10-21)
react/lib/util/address.ts (1)
  • getAddressPrefix (52-64)
react/lib/components/Widget/Widget.tsx (2)
react/lib/util/chronik.ts (1)
  • getTokenInfo (261-272)
react/lib/util/address.ts (1)
  • isValidCashAddress (4-13)
🪛 Biome (2.1.2)
react/lib/util/chronik.ts

[error] 265-265: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 Gitleaks (8.30.0)
docs/README.md

[high] 1143-1143: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1149-1149: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1155-1155: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 markdownlint-cli2 (0.18.1)
docs/zh-tw/README.md

1135-1135: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

⏰ 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: Run build
🔇 Additional comments (12)
docs/_sidebar.md (1)

37-38: LGTM! Documentation sidebar updated correctly.

The token-id entry has been properly added to the Parameters section, maintaining consistency with the existing sidebar structure.

docs/zh-tw/_sidebar.md (1)

35-37: LGTM! Traditional Chinese sidebar properly updated.

The donation-rate and token-id entries have been added consistently with the main documentation sidebar structure.

react/lib/util/chronik.ts (4)

169-187: LGTM! Token-aware transaction conversion implemented correctly.

The function properly accepts an optional tokenId, fetches token amount when provided, and substitutes it for the base amount in the returned Transaction object. The tokenAmount calculation call on line 174 and conditional assignment on line 177 work together correctly.


261-272: LGTM! Token info retrieval function is well-structured.

The getTokenInfo function correctly derives the network slug from the address, initializes a Chronik client, and fetches token metadata.

Note: The Biome static analysis error flagging useStrategy as a hook violation is a false positive—ChronikClient.useStrategy is a Chronik client method, not a React hook.


274-297: LGTM! WebSocket message parsing properly threaded for token support.

The tokenId parameter is correctly added to parseWebsocketMessage and passed through to getTransactionFromChronikTransaction on line 290, enabling token-aware transaction resolution from WebSocket messages.


299-326: LGTM! Chronik WebSocket initialization supports tokenId.

The function signature correctly accepts the optional tokenId parameter and forwards it to parseWebsocketMessage on line 318, completing the token-aware WebSocket plumbing.

react/lib/components/Widget/Widget.tsx (6)

57-57: LGTM! Token infrastructure properly integrated into Widget.

The tokenId prop flows cleanly through the component:

  • Imported getTokenInfo utility
  • Added tokenId to WidgetProps interface
  • Destructured from props
  • New tokenName state to hold token ticker

The integration follows existing patterns and maintains type safety.

Also applies to: 120-120, 181-181, 337-337


507-509: LGTM! Token icon URL helper is straightforward.

The getTokenIconUrl helper constructs URLs for fetching token icons from icons.etokens.cash, using the standard 128px size.


601-619: LGTM! Token info fetching includes proper error handling.

The effect correctly:

  • Fetches token info only when tokenId is present
  • Extracts and stores the token ticker
  • Handles errors gracefully by clearing tokenName and setting an error message
  • Ensures loading state is cleared in the finally block

This addresses the error handling concerns from the previous review.


837-837: LGTM! Token-aware text rendering and URL generation.

The component correctly:

  • Displays tokenName instead of currency code when tokenId is present (lines 862, 866)
  • Passes tokenId to resolveUrl for proper URL construction (lines 837, 864, 867)

The fallback logic ensures graceful degradation when tokenName is not yet loaded.

Also applies to: 862-867


993-1038: LGTM! URL generation correctly handles token-specific parameters.

The resolveUrl function appropriately:

  • Uses token_decimalized_qty instead of amount when tokenId is present (lines 1011, 1018)
  • Skips donation address logic for tokens (lines 1010-1015), consistent with PR objectives noting token donation support is future work
  • Always appends token_id parameter when tokenId is present (lines 1030-1033)

The token-aware URL construction aligns with the payment protocol requirements.


1088-1088: LGTM! QR code displays token icon when appropriate.

The QR code image source correctly prioritizes:

  1. Success checkmark when payment succeeds
  2. Token icon (via getTokenIconUrl) when tokenId is present
  3. BCH or XEC logo based on address type

The conditional chaining ensures the right icon appears in each scenario.


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.

@lissavxo lissavxo marked this pull request as ready for review December 23, 2025 22:33
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: 2

Caution

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

⚠️ Outside diff range comments (1)
react/lib/util/chronik.ts (1)

166-184: Add error handling for token amount calculation.

The call to getTokenAmount (line 171) lacks error handling. If token amount calculation fails, it will throw an unhandled error.

🔎 Recommended fix
 const getTransactionFromChronikTransaction = async (transaction: Tx, address: string, tokenId?: string): Promise<Transaction> => {
     const { amount, opReturn } = await getTransactionAmountAndData(transaction, address)
     const parsedOpReturn = resolveOpReturn(opReturn)
     const networkSlug = getAddressPrefix(address)
     const inputAddresses = getSortedInputAddresses(networkSlug, transaction)
-    const tokenAmount = tokenId ? await getTokenAmount(transaction, tokenId, address, networkSlug) : undefined;
+    let tokenAmount: string | undefined;
+    if (tokenId) {
+      try {
+        // Fetch token decimals from token info
+        const tokenInfo = await getTokenInfo(tokenId, address);
+        const decimals = tokenInfo.genesisInfo.decimals;
+        tokenAmount = await getTokenAmount(transaction, tokenId, address, networkSlug, decimals);
+      } catch (err) {
+        console.error('Failed to calculate token amount:', err);
+        tokenAmount = '0';
+      }
+    }
     return {
       hash: transaction.txid,
       amount: tokenId ? tokenAmount! : amount,
       address,
       timestamp: transaction.block !== undefined ? transaction.block.timestamp : transaction.timeFirstSeen,
       confirmed: transaction.block !== undefined,
       opReturn,
       paymentId: parsedOpReturn?.paymentId ?? '',
       message: parsedOpReturn?.message ?? '',
       rawMessage: parsedOpReturn?.rawMessage ?? '',
       inputAddresses,
     }
 }
🧹 Nitpick comments (3)
react/lib/components/Widget/Widget.tsx (2)

501-503: Consider adding validation for the tokenId parameter.

The helper constructs an icon URL without validating the tokenId. While image loading failures are typically handled by the browser, consider adding basic validation (non-empty, expected format) to avoid constructing invalid URLs.

🔎 Optional: Add basic validation
 const getTokenIconUrl = useCallback((tokenId: string): string => {
+  if (!tokenId || typeof tokenId !== 'string' || tokenId.trim() === '') {
+    throw new Error('Invalid tokenId provided');
+  }
   return `https://icons.etokens.cash/128/${tokenId}.png`
 }, [])

845-845: Handle null tokenName in display text.

If tokenName is null (e.g., token info fetch failed or tokenTicker is undefined), the display text will show "null" to users. Consider providing a fallback.

🔎 Proposed fix
-setText(`Send ${amountToDisplay} ${tokenId ? tokenName : cur}`)
+setText(`Send ${amountToDisplay} ${tokenId ? (tokenName || 'token') : cur}`)
-setText(`Send any amount of ${tokenId ? tokenName : thisAddressType}`)
+setText(`Send any amount of ${tokenId ? (tokenName || 'token') : thisAddressType}`)

Also applies to: 849-850

react/lib/util/chronik.ts (1)

258-269: Consider documenting error handling expectations.

The getTokenInfo function lacks error handling and will propagate errors to callers. While this is acceptable, ensure all call sites properly handle potential failures (network errors, invalid tokenId, etc.).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd9bbf5 and 3220fd0.

📒 Files selected for processing (6)
  • paybutton/src/index.tsx
  • react/lib/components/PayButton/PayButton.tsx
  • react/lib/components/PaymentDialog/PaymentDialog.tsx
  • react/lib/components/Widget/Widget.tsx
  • react/lib/util/chronik.ts
  • react/lib/util/socket.ts
🧰 Additional context used
🧬 Code graph analysis (2)
react/lib/util/socket.ts (2)
react/lib/util/chronik.ts (1)
  • initializeChronikWebsocket (296-323)
react/lib/util/types.ts (1)
  • Transaction (10-21)
react/lib/components/Widget/Widget.tsx (3)
react/lib/util/chronik.ts (1)
  • getTokenInfo (258-269)
react/lib/util/format.ts (1)
  • amount (5-12)
react/lib/util/address.ts (1)
  • isValidCashAddress (4-13)
🔇 Additional comments (10)
react/lib/util/socket.ts (1)

125-125: LGTM! Clean parameter propagation.

The tokenId parameter is properly threaded through the interface and passed to initializeChronikWebsocket, consistent with the token-aware flow.

Also applies to: 149-155

paybutton/src/index.tsx (1)

109-109: LGTM! Properly enables tokenId as a recognized prop.

The addition to allowedProps enables DOM attribute parsing and forwarding of tokenId to PayButton/Widget components.

react/lib/components/PaymentDialog/PaymentDialog.tsx (1)

70-70: LGTM! Clean prop forwarding.

The tokenId is properly threaded from PaymentDialogProps through to WidgetContainer, enabling token support in the dialog flow.

Also applies to: 134-134, 264-264

react/lib/components/PayButton/PayButton.tsx (1)

62-62: LGTM! Proper tokenId integration.

The tokenId is correctly added to PayButtonProps and forwarded to both the Chronik WebSocket setup (for transaction monitoring) and PaymentDialog (for UI rendering).

Also applies to: 97-97, 298-298, 456-456

react/lib/components/Widget/Widget.tsx (5)

1070-1070: LGTM! Proper QR code icon selection for tokens.

The image selection correctly prioritizes token icons when tokenId is present, falling back to the appropriate BCH or XEC logo based on the address format.


854-854: LGTM! Effect dependencies are complete.

The effect dependencies properly include both tokenName and tokenId, which are used within the effect for URL generation and display text.


546-546: LGTM! TokenId properly propagated to WebSocket setup.

The tokenId is correctly passed to the Chronik WebSocket setup for transaction monitoring in standalone widget mode.


55-55: LGTM! Proper setup for token support.

The imports, prop additions, and state initialization properly establish the foundation for token functionality in the Widget component.

Also applies to: 118-118, 179-179, 330-330


976-1021: Verify token payment URL parameter names with Cashtab protocol.

The code confirms that donation functionality is intentionally disabled for token payments (lines 993-1004 do not append donation address/amount parameters when tokenId is present), while donations work normally for XEC/BCH payments.

Please verify that token_decimalized_qty and token_id are the correct parameter names for the Cashtab wallet's token payment protocol.

react/lib/util/chronik.ts (1)

271-277: LGTM! Proper tokenId propagation through WebSocket flow.

The tokenId parameter is consistently threaded through the WebSocket initialization and message parsing functions, enabling token-aware transaction detection.

Also applies to: 287-287, 296-299, 314-315

@Klakurka Klakurka requested a review from chedieck December 23, 2025 22:39
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: 3

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3220fd0 and 5ce34ee.

📒 Files selected for processing (6)
  • docs/README.md
  • docs/_sidebar.md
  • docs/zh-cn/README.md
  • docs/zh-cn/_sidebar.md
  • docs/zh-tw/README.md
  • docs/zh-tw/_sidebar.md
🧰 Additional context used
🪛 Gitleaks (8.30.0)
docs/zh-tw/README.md

[high] 1138-1138: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1144-1144: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1150-1150: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

docs/README.md

[high] 1143-1143: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1149-1149: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1155-1155: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 markdownlint-cli2 (0.18.1)
docs/zh-tw/README.md

1135-1135: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


1135-1135: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1135-1135: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1141-1141: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1141-1141: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1147-1147: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1147-1147: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

docs/README.md

1139-1139: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


1139-1139: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1139-1139: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1145-1145: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1145-1145: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1151-1151: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1151-1151: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

🔇 Additional comments (3)
docs/_sidebar.md (1)

37-38: Navigation link correctly added.

The token-id sidebar entry is properly formatted and the anchor ?id=token-id corresponds to the ## token-id heading in the documentation.

docs/zh-tw/_sidebar.md (1)

35-37: Navigation sidebar properly localized.

The addition of both donation-rate and token-id entries improves consistency across localized sidebar versions.

docs/zh-cn/_sidebar.md (1)

36-37: Navigation entry properly added.

The token-id sidebar link is correctly positioned and formatted, maintaining consistency with other parameter navigation entries.

Comment on lines 1129 to 1157
## token-id

> **「tokenId」 参数用于定义在按钮或小组件中使用的 eToken。**
?> tokenId 参数是可选的。它接受一个包含 token ID 的字符串。默认值为 null。


**Example:**
<!-- tabs:start -->

#### ** HTML **

```html
token-id="c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```

#### ** JavaScript **

```javascript
tokenId: "c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```

#### ** React **

```react
tokenId = "c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```
<!-- tabs:end -->

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the actual file and look at the specific lines
cat -n docs/zh-cn/README.md | sed -n '1129,1157p'

Repository: PayButton/paybutton

Length of output: 761


🏁 Script executed:

# Also check if markdownlint is available and what it reports for this section
which markdownlint && markdownlint docs/zh-cn/README.md | grep -E "1129|1130|1131|1132|1133|1134|1135|1136|1137|1138|1139|1140|1141|1142|1143|1144|1145|1146|1147|1148|1149|1150|1151|1152|1153|1154|1155|1156|1157" || echo "markdownlint not available"

Repository: PayButton/paybutton

Length of output: 150


🏁 Script executed:

# Alternative: Check if there are linting tools in the repo
find . -name ".markdownlintrc*" -o -name "markdownlint*" -o -name ".lintrc*" | head -20

Repository: PayButton/paybutton

Length of output: 45


Fix markdown linting violations: spaces in tab heading emphasis markers and heading level increment.

The section contains formatting issues in the tab headings:

  • Lines 1139, 1145, 1151: Spaces inside emphasis markers (#### ** HTML ** should be #### **HTML**, etc.)
  • Heading structure jumps from h2 to h4, skipping h3 level
🤖 Prompt for AI Agents
In docs/zh-cn/README.md around lines 1129 to 1157, the tab heading emphasis
markers contain extraneous spaces and the heading level jumps from h2 to h4;
remove the spaces inside the emphasis markers so headings read like "####
**HTML**", "#### **JavaScript**", "#### **React**" (no spaces between ** and
text) and adjust the heading levels to follow the document structure (use h3 if
the section should be one level below the current h2, or otherwise ensure
headings increment sequentially) so linting and document hierarchy are correct.

Comment on lines 1125 to 1153
## token-id

> **「tokenId」 參數用於定義在按鈕或小工具中使用的 eToken。**
?> tokenId 參數為選填。它接受一個包含 token ID 的字串。預設值為 null。


**Example:**
<!-- tabs:start -->

#### ** HTML **

```html
token-id="c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```

#### ** JavaScript **

```javascript
tokenId: "c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```

#### ** React **

```react
tokenId = "c67bf5c2b6d91cfb46a5c1772582eff80d88686887be10aa63b0945479cf4ed4"
```
<!-- tabs:end -->

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if markdownlint is available, if not install it
if ! command -v markdownlint &> /dev/null; then
    npm install -g markdownlint-cli 2>/dev/null || pip install markdownlint-md 2>/dev/null || echo "Installing markdownlint..."
fi

# Check markdown files exist
echo "=== Checking files ===" 
ls -la docs/zh-tw/README.md docs/zh-cn/README.md 2>&1 | head -20

# Get line counts for context
echo -e "\n=== File line counts ===" 
wc -l docs/zh-tw/README.md docs/zh-cn/README.md

# Extract the token-id section from zh-tw file (lines 1125-1153)
echo -e "\n=== zh-tw token-id section (lines 1125-1153) ===" 
sed -n '1125,1153p' docs/zh-tw/README.md | cat -n

# Extract the same section from zh-cn for comparison
echo -e "\n=== zh-cn token-id section (approx same location) ===" 
sed -n '1125,1153p' docs/zh-cn/README.md | cat -n

Repository: PayButton/paybutton

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Try to run markdownlint on the specific files if available
if command -v markdownlint &> /dev/null; then
    echo "=== Markdown linting results for zh-tw ===" 
    markdownlint docs/zh-tw/README.md 2>&1 | grep -A 10 "token-id" || markdownlint docs/zh-tw/README.md 2>&1 | grep -A 10 "1131\|1137\|1143" || markdownlint docs/zh-tw/README.md 2>&1 | head -50
fi

# Check for markdownlint configuration files
echo -e "\n=== Looking for markdownlint config ===" 
find . -maxdepth 3 -name ".markdownlint*" -o -name "markdownlint*" | head -10

Repository: PayButton/paybutton

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Read specific lines from zh-tw README
echo "=== Lines 1125-1153 from docs/zh-tw/README.md ===" 
sed -n '1125,1153p' docs/zh-tw/README.md

echo -e "\n=== Checking line 1131 specifically ===" 
sed -n '1131p' docs/zh-tw/README.md | cat -A

echo -e "\n=== Lines around emphasis markers (1131, 1137, 1143) ===" 
sed -n '1130,1145p' docs/zh-tw/README.md | cat -n

Repository: PayButton/paybutton

Length of output: 1078


🏁 Script executed:

#!/bin/bash
# Compare with zh-cn version
echo "=== Lines 1125-1153 from docs/zh-cn/README.md ===" 
sed -n '1125,1153p' docs/zh-cn/README.md

echo -e "\n=== Check line 1131 in zh-cn ===" 
sed -n '1131p' docs/zh-cn/README.md | cat -A

echo -e "\n=== Lines around emphasis markers in zh-cn (1131, 1137, 1143) ===" 
sed -n '1130,1145p' docs/zh-cn/README.md | cat -n

Repository: PayButton/paybutton

Length of output: 1248


Fix markdown linting violations in the token-id section.

The file contains markdown linting violations:

  • MD001: Heading level jump from ## to #### without intermediate ###
  • MD037: Spaces inside emphasis markers in ** HTML **, ** JavaScript **, and ** React **

These match the same violations present in the Simplified Chinese version.

🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 1138-1138: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1144-1144: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1150-1150: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 markdownlint-cli2 (0.18.1)

1135-1135: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


1135-1135: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1135-1135: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1141-1141: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1141-1141: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1147-1147: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


1147-1147: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

🤖 Prompt for AI Agents
In docs/zh-tw/README.md around lines 1125 to 1153, the token-id section has
markdown lint issues: the subheadings jump from ## to #### and the bold labels
contain spaces inside the emphasis markers. Fix by adjusting the subheading
levels to follow ## -> ### (or add a ### level before the current ####) so
headings increment by one level, and remove spaces inside the bold markers
(change "** HTML **", "** JavaScript **", "** React **" to "**HTML**",
"**JavaScript**", "**React**") to satisfy MD001 and MD037.

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: 0

Caution

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

⚠️ Outside diff range comments (1)
react/lib/components/Widget/Widget.tsx (1)

1026-1028: Missing tokenId in resolveUrl dependency array.

The resolveUrl callback uses tokenId (lines 1000, 1007, 1020) but it's not included in the dependency array at line 1027. This could cause stale closure issues if tokenId changes after initial render.

🔎 Proposed fix
-    [disabled, to, opReturn, userDonationRate, donationAddress, donationEnabled, shouldApplyDonation]
+    [disabled, to, opReturn, userDonationRate, donationAddress, donationEnabled, shouldApplyDonation, tokenId]
♻️ Duplicate comments (1)
react/lib/util/chronik.ts (1)

116-135: Precision loss from integer division inside the loop.

The division by BigInt(divisor) at line 129 happens per-output before summing. BigInt division truncates, so this causes precision loss when multiple outputs exist.

For example with 2 decimals: two outputs of 150 atoms each would calculate as (150/100) + (150/100) = 1 + 1 = 2 instead of (150+150)/100 = 3.

🔎 Proposed fix: sum atoms first, then divide
 const getTokenAmount = async (transaction: Tx, tokenId: string, address: string, networkSlug: string): Promise<string> => {
   let totalTokenOutput = BigInt(0);
   const tokenInfo = await getTokenInfo(tokenId, address);
   const decimals = tokenInfo.genesisInfo.decimals;
   const divisor = 10 ** decimals;
   
   for (const output of transaction.outputs) {
     if (output.token?.tokenId === tokenId) {
       const outputAddress = outputScriptToAddress(networkSlug, output.outputScript)

       if(outputAddress === address) {
         const atoms = BigInt(output.token.atoms);
-        totalTokenOutput += atoms / BigInt(divisor);
+        totalTokenOutput += atoms;
       }
     }
   }

-  return totalTokenOutput.toString();
+  // Convert from atoms to decimalized string
+  const divisorBigInt = BigInt(divisor);
+  const integerPart = totalTokenOutput / divisorBigInt;
+  const remainder = totalTokenOutput % divisorBigInt;
+  
+  if (remainder === BigInt(0) || decimals === 0) {
+    return integerPart.toString();
+  }
+  
+  const remainderStr = remainder.toString().padStart(decimals, '0');
+  return `${integerPart}.${remainderStr}`.replace(/\.?0+$/, '');
 }
🧹 Nitpick comments (2)
react/lib/util/chronik.ts (2)

169-187: Non-null assertion on tokenAmount is acceptable but consider defensive handling.

Line 177's tokenAmount! assertion relies on the prior conditional at line 174. While this works, if getTokenAmount were to return undefined unexpectedly (e.g., API failure), this would throw at runtime.

🔎 Optional: defensive fallback
-      amount: tokenId ? tokenAmount! : amount,
+      amount: tokenId && tokenAmount ? tokenAmount : amount,

261-272: Consider caching token info or reusing ChronikClient.

getTokenInfo creates a new ChronikClient on every call. When getTokenAmount calls this internally for each transaction, it results in redundant client instantiation and API calls for the same token.

Consider memoizing token info or passing the client/token info as a parameter to avoid repeated lookups for the same tokenId.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce34ee and d762668.

📒 Files selected for processing (5)
  • docs/README.md
  • docs/zh-cn/README.md
  • docs/zh-tw/README.md
  • react/lib/components/Widget/Widget.tsx
  • react/lib/util/chronik.ts
🧰 Additional context used
🧬 Code graph analysis (2)
react/lib/util/chronik.ts (3)
react/lib/util/types.ts (1)
  • Transaction (10-21)
react/lib/util/address.ts (1)
  • getAddressPrefix (52-64)
react/lib/util/format.ts (1)
  • amount (5-12)
react/lib/components/Widget/Widget.tsx (1)
react/lib/util/chronik.ts (1)
  • getTokenInfo (261-272)
🪛 Biome (2.1.2)
react/lib/util/chronik.ts

[error] 265-265: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 Gitleaks (8.30.0)
docs/README.md

[high] 1143-1143: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1149-1149: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1155-1155: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

docs/zh-cn/README.md

[high] 1142-1142: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1148-1148: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1154-1154: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

docs/zh-tw/README.md

[high] 1138-1138: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1144-1144: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 1150-1150: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 markdownlint-cli2 (0.18.1)
docs/zh-cn/README.md

1135-1135: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

🔇 Additional comments (6)
docs/zh-cn/README.md (1)

1129-1157: Token-id documentation looks good.

The new token-id parameter documentation is clear and consistent with the existing parameter sections. The Gitleaks warnings about "Generic API Key" on the example token ID strings are false positives—these are clearly documentation examples showing the expected format, not actual secrets.

The heading level increment (h2 to h4) flagged by markdownlint follows the same docsify tabs pattern used throughout this document for other parameters.

docs/zh-tw/README.md (1)

1125-1155: Token-id documentation is consistent with other locales.

The Traditional Chinese documentation for the token-id parameter is well-structured and consistent with the Simplified Chinese and English versions. The Gitleaks warnings are false positives for documentation example strings.

docs/README.md (1)

1130-1158: Token-id documentation is complete and accurate.

The English documentation for the token-id parameter correctly describes its purpose, optionality, and default value. Examples are provided for all three integration methods (HTML, JavaScript, React). The Gitleaks warnings are false positives for documentation example strings.

react/lib/components/Widget/Widget.tsx (3)

595-613: Token info fetching now has proper error handling.

The effect correctly implements try/catch/finally with appropriate error state management. The redundant tokenId !== null check mentioned in the past review has also been removed.


1000-1011: Donation is not applied to token payment URLs.

When tokenId is present and shouldApplyDonation returns true, the donation address and amount are not included in the URL (lines 1001-1002). Only token_decimalized_qty is added.

This might be intentional if token payment URLs don't support multi-output donations, but it means the donation UI will show the donation rate even though it won't be applied.

Please verify this is the intended behavior. If donations should be hidden for token payments, consider disabling the donation UI when tokenId is present.


1069-1085: Token icon integration in QR code looks good.

The icon selection logic correctly handles all cases: success state shows checkmark, token payments show token icon, otherwise appropriate chain icon based on address type.

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: 0

🧹 Nitpick comments (1)
react/lib/components/Widget/Widget.tsx (1)

595-613: Remove redundant check and handle null tokenName display.

Two minor improvements:

  1. The check tokenId !== '' on line 597 is redundant since an empty string is falsy
  2. When tokenName is null (during loading, on error, or if tokenTicker is missing), the UI text on lines 852 and 856 will display the string "null"
🔎 Suggested improvements
  useEffect(() => {
    ;(async (): Promise<void> => {
-     if (tokenId && tokenId !== '') {
+     if (tokenId) {
        try {
          const tokenInfo = await getTokenInfo(tokenId, to)
-         const name = tokenInfo.genesisInfo.tokenTicker ?? null
+         const name = tokenInfo.genesisInfo.tokenTicker ?? 'Token'
          setTokenName(name)
        } catch (err) {
          console.error('Failed to fetch token info:', err)
-         setTokenName(null)
+         setTokenName('Token')
          setErrorMsg('Unable to load token information')
        } finally {
          setLoading(false)
        }
        return
      }
      setLoading(false)
    })()
  }, [tokenId, to])

Alternatively, update the text rendering to provide a fallback:

- setText(`Send ${amountToDisplay} ${tokenId ? tokenName : cur}`)
+ setText(`Send ${amountToDisplay} ${tokenId ? (tokenName || 'Token') : cur}`)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d762668 and 356e9e8.

📒 Files selected for processing (1)
  • react/lib/components/Widget/Widget.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
react/lib/components/Widget/Widget.tsx (2)
react/lib/util/chronik.ts (1)
  • getTokenInfo (261-272)
react/lib/util/address.ts (1)
  • isValidCashAddress (4-13)
🔇 Additional comments (5)
react/lib/components/Widget/Widget.tsx (5)

55-55: LGTM!

The tokenId prop is correctly added to the interface and properly imported/destructured.

Also applies to: 118-118, 179-179


330-330: LGTM!

The tokenName state and getTokenIconUrl helper are implemented correctly.

Also applies to: 501-503


546-546: LGTM!

The tokenId is correctly passed to the websocket setup, and the effect dependencies are properly updated to include both tokenName and tokenId.

Also applies to: 861-861


983-1028: Verify donation support for token payments.

The URL generation logic differs between regular and token payments when donations are enabled. For token payments (lines 1000-1005), only token_decimalized_qty is added without the donation address and amount parameters that are included for regular payments (line 1004).

Is this intentional? If token payments don't support donations to the PayButton team, this behavior is correct. Otherwise, the donation parameters should be included for token payments as well.


1077-1077: LGTM!

The QR code correctly displays the token icon when tokenId is present, with appropriate fallbacks for success state and regular BCH/XEC addresses.

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.

2 participants