Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
175 changes: 146 additions & 29 deletions examples/link-address/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@ export function App() {
const [isLoading, setIsLoading] = useState(false)
const [isLinking, setIsLinking] = useState(false)
const [isUnlinking, setIsUnlinking] = useState(false)
const [isSettingPrimary, setIsSettingPrimary] = useState(false)
const [linkedAddresses, setLinkedAddresses] = useState<string[]>([])

const { activeAddress, transactionSigner } = useWallet()
const { activeAddress, activeWalletAddresses, transactionSigner } =
useWallet()

const handleNfdInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNfdData(null)
setNfdName(e.target.value)
setLinkedAddresses([])
setAddressToLink('')
}

const handleAddressInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleAddressSelectChange = (
e: React.ChangeEvent<HTMLSelectElement>,
) => {
setAddressToLink(e.target.value)
}

Expand All @@ -36,6 +41,7 @@ export function App() {
setError('')
setNfdData(null)
setLinkedAddresses([])
setAddressToLink('')
setIsLoading(true)

try {
Expand Down Expand Up @@ -81,8 +87,8 @@ export function App() {
throw new Error('No NFD selected')
}

if (!addressToLink.trim()) {
throw new Error('Please enter an address to link')
if (!addressToLink) {
throw new Error('Please select an address to link')
}

// Check if the address is already linked
Expand All @@ -95,6 +101,16 @@ export function App() {
throw new Error('Only the owner can link addresses to this NFD')
}

// Check if the address to link is in the available addresses
if (
!activeWalletAddresses ||
!activeWalletAddresses.includes(addressToLink)
) {
throw new Error(
'The address to link must be one of your connected wallet addresses',
)
}

/**
* Link the address to the NFD
*/
Expand Down Expand Up @@ -170,6 +186,58 @@ export function App() {
}
}

const handleSetPrimaryAddress = async (address: string) => {
setError('')
setIsSettingPrimary(true)

try {
if (!activeAddress) {
throw new Error('No active address')
}

if (!nfdData) {
throw new Error('No NFD selected')
}

// Check if the active address is the owner
if (nfdData.owner !== activeAddress) {
throw new Error(
'Only the owner can set the primary address for this NFD',
)
}

/**
* Set the address as primary for the NFD
*/
const updatedNfd = await nfd
.setSigner(activeAddress, transactionSigner)
.manage(nfdName)
.setPrimaryAddress(address)

setNfdData(updatedNfd)

// Update the linked addresses list
const addresses: string[] = []
if (updatedNfd.caAlgo && updatedNfd.caAlgo.length > 0) {
updatedNfd.caAlgo.forEach((addr) => {
if (addr) {
addresses.push(addr)
}
})
}
setLinkedAddresses(addresses)
} catch (err) {
setError(parseTransactionError(err))
console.error(err)
} finally {
setIsSettingPrimary(false)
}
}

const availableAddresses = activeWalletAddresses
? activeWalletAddresses.filter((addr) => !linkedAddresses.includes(addr))
: []

return (
<div>
<h1>NFD Address Linker</h1>
Expand Down Expand Up @@ -245,28 +313,53 @@ export function App() {
<p>No addresses linked to this NFD.</p>
) : (
<ul style={{ paddingLeft: '20px' }}>
{linkedAddresses.map((address) => (
<li key={address} style={{ marginBottom: '8px' }}>
{linkedAddresses.map((address, index) => (
<li key={address} style={{ marginBottom: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<span
style={{
fontFamily: 'monospace',
marginRight: '10px',
marginRight: '8px',
wordBreak: 'break-all',
}}
>
{address}
</span>
{nfdData.owner === activeAddress && (
<button
onClick={() => handleUnlinkAddress(address)}
disabled={isUnlinking}
{index === 0 && (
<span
style={{
padding: '2px 8px',
fontSize: '12px',
border: '1px solid #4361ee',
backgroundColor: '#f1f5ff',
borderRadius: '12px',
padding: '2px 6px',
fontSize: '0.85em',
marginRight: '8px',
color: '#4361ee',
}}
>
{isUnlinking ? 'Unlinking...' : 'Unlink'}
</button>
Primary
</span>
)}
{nfdData.owner === activeAddress && (
<div style={{ display: 'flex', gap: '4px' }}>
{index > 0 && (
<button
onClick={() =>
handleSetPrimaryAddress(address)
}
disabled={isSettingPrimary}
style={{ marginRight: '4px' }}
>
Set Primary
</button>
)}
<button
onClick={() => handleUnlinkAddress(address)}
disabled={isUnlinking}
>
Unlink
</button>
</div>
)}
</div>
</li>
Expand All @@ -278,20 +371,44 @@ export function App() {
{nfdData.owner === activeAddress && (
<div style={{ marginTop: '20px' }}>
<h3>Link New Address</h3>
<div style={{ display: 'flex', gap: '10px' }}>
<input
type="text"
value={addressToLink}
onChange={handleAddressInputChange}
placeholder="Enter Algorand address to link"
style={{ width: '350px' }}
/>
<button
onClick={handleLinkAddress}
disabled={isLinking || !addressToLink.trim()}
>
{isLinking ? 'Linking...' : 'Link Address'}
</button>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
flexWrap: 'wrap',
}}
>
{availableAddresses.length > 0 ? (
<select
value={addressToLink}
onChange={handleAddressSelectChange}
style={{ width: '350px' }}
>
<option value="">Select an address to link</option>
{availableAddresses.map((address) => (
<option key={address} value={address}>
{address}
</option>
))}
</select>
) : (
<p style={{ margin: 0 }}>
No available addresses to link.
</p>
)}
<div>
<button
onClick={handleLinkAddress}
disabled={
isLinking ||
!addressToLink ||
availableAddresses.length === 0
}
>
{isLinking ? 'Linking...' : 'Link Address'}
</button>
</div>
</div>
</div>
)}
Expand Down
5 changes: 3 additions & 2 deletions examples/link-address/src/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,19 @@ const ConnectedWallet = ({ wallet }: { wallet: Wallet }) => {
<select
value={wallet.activeAccount?.address}
onChange={(e) => wallet.setActiveAccount(e.target.value)}
style={{ maxWidth: '640px', padding: '2px' }}
>
{wallet.accounts.map((account) => (
<option key={account.address} value={account.address}>
{account.name}
{account.address}
</option>
))}
</select>
)}

{wallet.activeAccount && (
<div className="account-info">
<span>{wallet.activeAccount.address}</span>
Active Account: {wallet.activeAccount.address}
</div>
)}

Expand Down
5 changes: 3 additions & 2 deletions examples/mint/src/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,19 @@ const ConnectedWallet = ({ wallet }: { wallet: Wallet }) => {
<select
value={wallet.activeAccount?.address}
onChange={(e) => wallet.setActiveAccount(e.target.value)}
style={{ maxWidth: '640px', padding: '2px' }}
>
{wallet.accounts.map((account) => (
<option key={account.address} value={account.address}>
{account.name}
{account.address}
</option>
))}
</select>
)}

{wallet.activeAccount && (
<div className="account-info">
<span>{wallet.activeAccount.address}</span>
Active Account: {wallet.activeAccount.address}
</div>
)}

Expand Down
5 changes: 3 additions & 2 deletions examples/set-metadata/src/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,19 @@ const ConnectedWallet = ({ wallet }: { wallet: Wallet }) => {
<select
value={wallet.activeAccount?.address}
onChange={(e) => wallet.setActiveAccount(e.target.value)}
style={{ maxWidth: '640px', padding: '2px' }}
>
{wallet.accounts.map((account) => (
<option key={account.address} value={account.address}>
{account.name}
{account.address}
</option>
))}
</select>
)}

{wallet.activeAccount && (
<div className="account-info">
<span>{wallet.activeAccount.address}</span>
Active Account: {wallet.activeAccount.address}
</div>
)}

Expand Down
7 changes: 5 additions & 2 deletions packages/sdk/src/modules/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ export abstract class BaseModule {

/**
* Ensure a signer is set before proceeding
* @returns The current signer
* @throws If no signer is set
*/
protected requireSigner(): void {
if (!this.getSigner()) {
protected requireSigner(): TransactionSignerAccount {
const signer = this.getSigner()
if (!signer) {
throw new Error('Signer required. Call setSigner() first.')
}
return signer
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/src/modules/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Address } from 'algosdk'

import { isZeroBytes } from '../utils/internal/bytes'
import { determineNfdState, generateMetaTags } from '../utils/internal/nfd'
import { parseAddress, parseString, parseUint64 } from '../utils/internal/state'
import { isValidName } from '../utils/nfd'
Expand Down Expand Up @@ -179,6 +180,12 @@ export class LookupModule extends BaseModule {
// Split the value into chunks of 32 bytes
for (let i = 0; i < value.length; i += PUBLIC_KEY_LENGTH) {
const publicKey = value.slice(i, i + PUBLIC_KEY_LENGTH)

// Skip zero addresses (all bytes are zero)
if (isZeroBytes(publicKey)) {
continue
}

try {
const address = new Address(publicKey).toString()
if (address) {
Expand All @@ -193,6 +200,11 @@ export class LookupModule extends BaseModule {
)
}
}

// Set the verified.caAlgo property as a comma-delimited string
if (caAlgo.length > 0) {
verified.caAlgo = caAlgo.join(',')
}
} catch (error) {
console.error('Failed to parse Algorand addresses from box:', error)
}
Expand Down
Loading