Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
11295f8
test(controller): cover UpdateTelemetry nil account from failed GetAc…
erhnysr Aug 21, 2026
ed2dbfd
fix(controller): guard against nil account in UpdateTelemetry
erhnysr Aug 21, 2026
466af34
fix(explorer): support copying identifiers on HTTP origins
moatus Sep 16, 2026
4165080
fix(explorer): address iOS clipboard review feedback
moatus Sep 19, 2026
4ef7b93
Merge pull request #615 from moatus/fix/explorer-http-clipboard
pablocampogo Sep 21, 2026
6797f0a
Merge branch 'development' into fix/update-telemetry-nil-account
pablocampogo Sep 22, 2026
8c617a6
chore: delete changelog requirement in contributing guide
pablocampogo Sep 22, 2026
35f0d18
Merge pull request #619 from canopy-network/delete-changelog-requirement
pablocampogo Sep 22, 2026
e6c6e0d
Merge branch 'development' into fix/update-telemetry-nil-account
pablocampogo Sep 22, 2026
d21a689
fix(controller): log when account lookup returns nil in UpdateTelemetry
erhnysr Sep 22, 2026
6604eed
fix(controller): wire a null logger into the GetAccount-error test co…
erhnysr Sep 23, 2026
a535b03
fix: use hex for plugin protobuf JSON bytes
andrewnguyen22 Sep 23, 2026
f7996c2
fix: enforce hex across protobuf transaction JSON
andrewnguyen22 Sep 23, 2026
4f00fef
test: remove native JSON regression coverage
andrewnguyen22 Sep 23, 2026
318c90a
Revert "test: remove native JSON regression coverage"
pablocampogo Sep 23, 2026
217849b
feat: add changes to the other plugins
pablocampogo Sep 23, 2026
0cde689
test: move native JSON coverage into message tests
andrewnguyen22 Sep 23, 2026
ae52385
docs: document hex JSON in TypeScript tutorial
andrewnguyen22 Sep 23, 2026
f660fc4
fix: tutorial fixes and increase ts timeout
pablocampogo Sep 23, 2026
32b3636
chore: bump version
pablocampogo Sep 23, 2026
cd47104
Merge pull request #535 from erhnysr/fix/update-telemetry-nil-account
pablocampogo Sep 23, 2026
c99f28d
Merge branch 'development' of github.com:canopy-network/canopy into f…
pablocampogo Sep 23, 2026
0ce2acf
Merge pull request #624 from canopy-network/fix/global-protobuf-json-…
pablocampogo Sep 24, 2026
22d9b01
Merge branch 'main' into development
pablocampogo Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ discuss your intended approach for solving the problem in the comments for an ex

- **Add documentation.** Document your changes with code doc comments or in existing guides.

- **Update the CHANGELOG** for all enhancements and bug fixes. Include the corresponding date, issue
number if one exists and current version if any. Check the format of the [CHANGELOG](.docs/CHANGELOG.md).

- **Target the `development` branch.** The repo's default branch is still `main`, but do **not**
open pull requests against `main`. Branch from and
[submit your pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
Expand Down
2 changes: 1 addition & 1 deletion cmd/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const (

// uses golang's semver naming convention
// https://pkg.go.dev/golang.org/x/mod/semver
SoftwareVersion = "v0.1.27+beta"
SoftwareVersion = "v0.1.28+beta"
ContentType = "Content-MessageType"
ApplicationJSON = "application/json; charset=utf-8"

Expand Down
1 change: 1 addition & 0 deletions cmd/rpc/web/explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"test:clipboard": "node --test tests/clipboard.test.cjs",
"preview": "vite preview",
"type-check": "tsc --noEmit"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { Check, Copy } from 'lucide-react'
import toast from 'react-hot-toast'
import { copyText } from '../../lib/clipboard'
import AnimatedNumber from '../AnimatedNumber'
import accountDetailTexts from '../../data/accountDetail.json'
import { cnpyDetailFormat, toCNPY } from '../../lib/utils'
Expand Down Expand Up @@ -37,11 +39,11 @@ const AccountDetailHeader: React.FC<AccountDetailHeaderProps> = ({ account }) =>

const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(account.address)
await copyText(account.address)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy address:', err)
} catch {
toast.error('Unable to copy address')
}
}

Expand Down
25 changes: 15 additions & 10 deletions cmd/rpc/web/explorer/src/components/block/BlockDetailInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { Copy } from 'lucide-react'
import toast from 'react-hot-toast'
import { copyText } from '../../lib/clipboard'
import blockDetailTexts from '../../data/blockDetail.json'
import { GREEN_BADGE_CLASS } from '../ui/badgeStyles'
import { formatCNPY } from '../../lib/utils'
Expand All @@ -26,16 +27,20 @@ const CopySymbol = () => <Copy aria-hidden="true" className="h-3.5 w-3.5" stroke

const BlockDetailInfo: React.FC<BlockDetailInfoProps> = ({ block, blockData }) => {
const [viewMode, setViewMode] = useState<'decoded' | 'raw'>('decoded')
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
const copyToClipboard = async (text: string) => {
try {
await copyText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
} catch {
toast.error('Unable to copy to clipboard')
}
}

const formatTimestamp = (timestamp: string) => {
Expand Down
11 changes: 8 additions & 3 deletions cmd/rpc/web/explorer/src/components/block/BlockSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { motion } from 'framer-motion'
import { Copy } from 'lucide-react'
import toast from 'react-hot-toast'
import { copyText } from '../../lib/clipboard'
import blockDetailTexts from '../../data/blockDetail.json'

interface BlockSidebarProps {
Expand Down Expand Up @@ -31,10 +32,14 @@ const BlockSidebar: React.FC<BlockSidebarProps> = ({
return `${address.slice(0, startLength)}...${address.slice(-endLength)}`
}

const copyToClipboard = (text: string, label: string = 'Address') => {
const copyToClipboard = async (text: string, label: string = 'Address') => {
if (text && text !== 'N/A') {
navigator.clipboard.writeText(text)
toast.success(`${label} copied to clipboard`)
try {
await copyText(text)
toast.success(`${label} copied to clipboard`)
} catch {
toast.error('Unable to copy to clipboard')
}
}
}

Expand Down
11 changes: 8 additions & 3 deletions cmd/rpc/web/explorer/src/components/search/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Link } from 'react-router-dom'
import { copyText } from '../../lib/clipboard'
import AnimatedNumber from '../AnimatedNumber'
import toast from 'react-hot-toast'
import { Account, TransactionsBySender, TransactionsByRec } from '../../lib/api'
Expand Down Expand Up @@ -30,10 +31,14 @@ const formatAccountBalance = (amount: number | undefined) =>
maximumFractionDigits: 2,
})

const copyToClipboard = (text: string) => {
const copyToClipboard = async (text: string) => {
if (text && text !== 'N/A') {
navigator.clipboard.writeText(text)
toast.success('Copied to clipboard')
try {
await copyText(text)
toast.success('Copied to clipboard')
} catch {
toast.error('Unable to copy to clipboard')
}
}
}

Expand Down
25 changes: 15 additions & 10 deletions cmd/rpc/web/explorer/src/components/token-swaps/OrderDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion'
import { Copy } from 'lucide-react'
import { copyText } from '../../lib/clipboard'
import { useOrder } from '../../hooks/useApi'
import toast from 'react-hot-toast'

Expand Down Expand Up @@ -32,16 +33,20 @@ const OrderDetailPage: React.FC = () => {
const numericCommittee = Number(committeeParam)
const { data: orderData, isLoading, error } = useOrder(numericCommittee, orderId || '')

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
const copyToClipboard = async (text: string) => {
try {
await copyText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
} catch {
toast.error('Unable to copy to clipboard')
}
}

const truncate = (str: string, n: number = 12) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion'
import { Copy } from 'lucide-react'
import { copyText } from '../../lib/clipboard'
import { useTxByHash, useBlockByHeight, useLatestBlock } from '../../hooks/useApi'
import toast from 'react-hot-toast'
import { format, formatDistanceToNow, parseISO, isValid } from 'date-fns'
Expand Down Expand Up @@ -132,16 +133,20 @@ const TransactionDetailPage: React.FC = () => {
return str.length > n * 2 ? `${str.slice(0, n)}…${str.slice(-8)}` : str
}

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
const copyToClipboard = async (text: string) => {
try {
await copyText(text)
toast.success('Copied to clipboard!', {
icon: '📋',
style: {
background: '#1a1a1a',
color: '#fafafa',
border: '1px solid #45ca46',
},
})
} catch {
toast.error('Unable to copy to clipboard')
}
}

const formatTimestamp = (timestamp: string | number) => {
Expand Down
19 changes: 2 additions & 17 deletions cmd/rpc/web/explorer/src/components/ui/CopyableIdentifier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { Copy } from 'lucide-react'
import { Link } from 'react-router-dom'
import toast from 'react-hot-toast'
import { copyText } from '../../lib/clipboard'

interface CopyableIdentifierProps {
value: string
Expand All @@ -12,25 +13,9 @@ interface CopyableIdentifierProps {
iconClassName?: string
}

const fallbackCopy = (value: string) => {
const textarea = document.createElement('textarea')
textarea.value = value
textarea.setAttribute('readonly', '')
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
}

const copyValue = async (value: string, label: string) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(value)
} else {
fallbackCopy(value)
}
await copyText(value)
toast.success(`${label} copied`)
} catch {
toast.error(`Unable to copy ${label.toLowerCase()}`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import { copyText } from '../../lib/clipboard'
import validatorDetailTexts from '../../data/validatorDetail.json'
import toast from 'react-hot-toast'
import { Copy } from 'lucide-react'
Expand Down Expand Up @@ -61,16 +62,20 @@ const ValidatorDetailHeader: React.FC<ValidatorDetailHeaderProps> = ({ validator
}
}

const copyToClipboard = (text: string, label: string = 'Address') => {
navigator.clipboard.writeText(text)
toast.success(`${label} copied to clipboard`, {
duration: 2000,
position: 'top-right',
style: {
background: '#171717',
color: '#35cd48',
},
})
const copyToClipboard = async (text: string, label: string = 'Address') => {
try {
await copyText(text)
toast.success(`${label} copied to clipboard`, {
duration: 2000,
position: 'top-right',
style: {
background: '#171717',
color: '#35cd48',
},
})
} catch {
toast.error('Unable to copy to clipboard')
}
}

// Determine button label and icon based on validator type
Expand Down
37 changes: 37 additions & 0 deletions cmd/rpc/web/explorer/src/lib/clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export async function copyText(text: string): Promise<void> {
if (window.isSecureContext && navigator.clipboard) {
try {
await navigator.clipboard.writeText(text)
return
} catch {
// Try the fallback when clipboard permissions are denied.
}
}

// The Clipboard API is unavailable on HTTP LAN addresses.
const textarea = document.createElement('textarea')
const activeElement = document.activeElement
textarea.value = text
textarea.contentEditable = 'true'
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
try {
textarea.select()
// iOS needs an editable element and an explicit selection range.
const range = document.createRange()
range.selectNodeContents(textarea)
const selection = window.getSelection()
selection?.removeAllRanges()
selection?.addRange(range)
textarea.setSelectionRange(0, text.length)
if (!document.execCommand('copy')) {
throw new Error('Unable to copy text')
}
} finally {
textarea.remove()
if (activeElement instanceof HTMLElement) {
activeElement.focus({ preventScroll: true })
}
}
}
31 changes: 5 additions & 26 deletions cmd/rpc/web/explorer/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { copyText } from './clipboard'

export const rowNavigationIgnoreSelector =
'a, button, input, select, textarea, summary, [role="button"], [data-row-click-ignore="true"]'

Expand Down Expand Up @@ -130,32 +132,9 @@ export function isEmpty(obj: object): boolean {

// copy() copies text to clipboard and triggers a toast notification
export function copy(state: any, setState: (state: any) => void, detail: string, toastText: string = "Copied!"): void {
if (navigator.clipboard && window.isSecureContext) {
// if HTTPS - use Clipboard API
navigator.clipboard
.writeText(detail)
.then(() => setState({ ...state, toast: toastText }))
.catch(() => fallbackCopy(state, setState, detail, toastText));
} else {
fallbackCopy(state, setState, detail, toastText);
}
}

// fallbackCopy() copies text to clipboard if clipboard API is unavailable
export function fallbackCopy(state: any, setState: (state: any) => void, detail: string, toastText: string = "Copied!"): void {
// if http - use textarea
const textArea = document.createElement("textarea");
textArea.value = detail;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand("copy");
setState({ ...state, toast: toastText });
} catch (err) {
console.error("Fallback copy failed", err);
setState({ ...state, toast: "Clipboard access denied" });
}
document.body.removeChild(textArea);
void copyText(detail)
.then(() => setState({ ...state, toast: toastText }))
.catch(() => setState({ ...state, toast: "Clipboard access denied" }));
}

// convertTx() sanitizes and simplifies a transaction object
Expand Down
Loading
Loading