Skip to content
Open
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
45 changes: 42 additions & 3 deletions examples/nextjs-delegated-access/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,51 @@ curl -X POST http://localhost:3000/api/delegation/sign \

### Triggering Delegation (Client-Side)

Delegation can be triggered in two ways:
Delegation can be triggered in three ways:

1. **Auto-prompt on sign-in** - Configure in the Dynamic dashboard to prompt users automatically
2. **Programmatically** - Use the `useWalletDelegation` hook:

2. **Modal UI** - Use `initDelegationProcess()` to open Dynamic's built-in delegation modal:

```typescript
import { useWalletDelegation } from "@dynamic-labs/sdk-react-core";

const { initDelegationProcess } = useWalletDelegation();

// Trigger delegation for the user's wallet
// Opens Dynamic's modal UI for delegation
await initDelegationProcess();

// Or delegate specific wallets only
await initDelegationProcess({ wallets: [primaryWallet] });
```

3. **Custom UI** - Use `delegateKeyShares()` for programmatic delegation without any UI:

```typescript
import { useWalletDelegation } from "@dynamic-labs/sdk-react-core";
import { ChainEnum } from "@dynamic-labs/sdk-api-core";

const { delegateKeyShares, getWalletsDelegatedStatus } = useWalletDelegation();

// Get wallets pending delegation
const pendingWallets = getWalletsDelegatedStatus().filter(
(wallet) => wallet.status === "pending"
);

// Delegate specific wallets (no UI shown)
await delegateKeyShares(
pendingWallets.map((wallet) => ({
chainName: wallet.chain as ChainEnum,
accountAddress: wallet.address,
}))
);

// Or delegate all pending wallets at once
await delegateKeyShares();
```

**Note:** Use `getWalletsDelegatedStatus()` to find eligible wallets. This returns all wallets with their delegation status (`"pending"`, `"delegated"`, or `"denied"`).

### Delegation Creation Flow

When a user approves delegation:
Expand Down Expand Up @@ -254,6 +285,14 @@ This approach combines:
## Project Structure

```text
components/
├── dynamic/
│ └── delegated-access/ # Client-side delegation UI
│ ├── index.tsx # Main entry with tabbed UI
│ ├── init.tsx # Modal UI (initDelegationProcess)
│ ├── management.tsx # Custom UI (delegateKeyShares)
│ ├── methods.tsx # Post-delegation actions
│ └── components/ # Shared UI components
lib/
├── dynamic/
│ ├── delegation/ # Delegation decryption & storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { ShieldCheck } from "lucide-react";

interface DelegationStatusHeaderProps {
isEnabled: boolean;
requiresDelegation?: boolean;
}

export default function DelegationStatusHeader({
isEnabled,
requiresDelegation,
}: DelegationStatusHeaderProps) {
return (
<div
Expand All @@ -27,12 +29,23 @@ export default function DelegationStatusHeader({
</p>
</div>
</div>
<div
className={`px-3 py-1.5 rounded-full text-xs font-semibold ${
isEnabled ? "bg-white text-green-600" : "bg-white/20 text-white/80"
}`}
>
{isEnabled ? "● Enabled" : "○ Disabled"}
<div className="flex items-center gap-2">
<div
className={`px-3 py-1.5 rounded-full text-xs font-semibold ${
requiresDelegation
? "bg-amber-100 text-amber-700"
: "bg-white/20 text-white"
}`}
>
{requiresDelegation ? "Required" : "Optional"}
</div>
<div
className={`px-3 py-1.5 rounded-full text-xs font-semibold ${
isEnabled ? "bg-white text-green-600" : "bg-white/20 text-white/80"
}`}
>
{isEnabled ? "● Enabled" : "○ Disabled"}
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
* - Shows loading state while SDK initializes
* - Displays wallet connection prompt if no wallet connected
* - Shows delegation status header with enabled/disabled state
* - Renders init flow for wallets pending delegation
* - Tabbed interface for Modal UI vs Custom UI delegation approaches
* - Renders methods panel for delegated wallets
*
* This is the main entry point for the delegation UI. Import this
* component wherever you want to display delegation functionality.
*/
"use client";

import { useState } from "react";
import { Sparkles, Code2 } from "lucide-react";
import {
SpinnerIcon,
useDynamicContext,
Expand All @@ -21,16 +23,20 @@ import {

import DelegatedAccessInit from "./init";
import DelegatedAccessMethods from "./methods";
import DelegationManagement from "./management";
import DelegationStatusHeader from "./components/delegation-status-header";
import ConnectedWalletInfo from "./components/connected-wallet-info";
import ConnectWalletPrompt from "./components/connect-wallet-prompt";
import WalletStatusTable from "./components/wallet-status-table";
import DelegationInfoBox from "@/components/info/delegation-info-box";

type DelegationTab = "modal" | "custom";

export default function DelegatedAccess() {
const { sdkHasLoaded, primaryWallet } = useDynamicContext();
const { delegatedAccessEnabled, getWalletsDelegatedStatus } =
const { delegatedAccessEnabled, getWalletsDelegatedStatus, requiresDelegation } =
useWalletDelegation();
const [activeTab, setActiveTab] = useState<DelegationTab>("modal");

if (!sdkHasLoaded) {
return (
Expand All @@ -49,7 +55,10 @@ export default function DelegatedAccess() {
<div className="space-y-6">
{/* Main Status Card */}
<div className="rounded-xl border bg-card overflow-hidden">
<DelegationStatusHeader isEnabled={delegatedAccessEnabled ?? false} />
<DelegationStatusHeader
isEnabled={delegatedAccessEnabled ?? false}
requiresDelegation={requiresDelegation}
/>

<div className={primaryWallet ? "p-6 space-y-4" : "px-4 py-2"}>
{primaryWallet ? (
Expand All @@ -63,10 +72,18 @@ export default function DelegatedAccess() {
</div>
</div>

{/* Conditional Components */}
{/* Tabbed Delegation Approaches */}
{primaryWallet && primaryWalletDelegationStatus?.status === "pending" && (
<DelegatedAccessInit />
<div className="space-y-4">
{/* Tab Navigation */}
<DelegationTabs activeTab={activeTab} onTabChange={setActiveTab} />

{/* Tab Content */}
{activeTab === "modal" ? <DelegatedAccessInit /> : <DelegationManagement />}
</div>
)}

{/* Methods Panel - shows when delegated */}
{primaryWallet &&
primaryWalletDelegationStatus?.status === "delegated" && (
<DelegatedAccessMethods />
Expand All @@ -76,3 +93,38 @@ export default function DelegatedAccess() {
</div>
);
}

function DelegationTabs({
activeTab,
onTabChange,
}: {
activeTab: DelegationTab;
onTabChange: (tab: DelegationTab) => void;
}) {
return (
<div className="flex gap-2 p-1 bg-muted rounded-lg">
<button
onClick={() => onTabChange("modal")}
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-md text-sm font-medium transition-all ${
activeTab === "modal"
? "bg-background text-dynamic shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
>
<Sparkles className="w-4 h-4" />
<span>Modal UI</span>
</button>
<button
onClick={() => onTabChange("custom")}
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-md text-sm font-medium transition-all ${
activeTab === "custom"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
>
<Code2 className="w-4 h-4" />
<span>Custom UI</span>
</button>
</div>
);
}
Loading