Skip to content

Commit 240df07

Browse files
committed
change some wallet signing modal display stuff
1 parent 2654ebc commit 240df07

17 files changed

Lines changed: 1213 additions & 871 deletions

File tree

packages/wallet-sdk/examples/demo-app/public/.well-known/sui/testnet/automatic-approval-policy.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schemaVersion": "1.0.0",
33
"operations": [
4-
{
4+
{
55
"id": "basic-transfers",
66
"description": "Allow basic SUI and coin transfers to any address",
77
"permissions": {

packages/wallet-sdk/examples/demo-app/src/app/demos/nft/transactions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function createMintNFTTransaction(params: {
1717
const tx = new Transaction();
1818

1919
// Add auto-approval intent for rule set selection
20-
tx.add(operationType('nft-creation'));
20+
tx.add(operationType('nft-operations'));
2121

2222
// Set sender
2323
tx.setSenderIfNotSet(params.senderAddress);

packages/wallet-sdk/examples/demo-app/src/app/demos/walrus/transactions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export async function createSwapSuiForWalTransaction(
2626
const tx = new Transaction();
2727

2828
// Add auto-approval intent for rule set selection
29-
tx.add(operationType('walrus-uploads'));
29+
tx.add(operationType('walrus-operations'));
3030

3131
tx.setSenderIfNotSet(senderAddress);
3232

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useCopy } from '../hooks/useCopy.js';
5+
6+
interface CopyableTextProps {
7+
text: string;
8+
className?: string;
9+
truncate?: boolean;
10+
maxLength?: number;
11+
}
12+
13+
export function CopyableText({
14+
text,
15+
className = '',
16+
truncate = false,
17+
maxLength = 50,
18+
}: CopyableTextProps) {
19+
const { copy, copied } = useCopy();
20+
21+
const displayText = truncate && text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
22+
23+
return (
24+
<div className={`group relative ${className}`}>
25+
<div className="flex items-center gap-2">
26+
<span className="font-mono text-xs break-all flex-1">{displayText}</span>
27+
<button
28+
onClick={() => copy(text)}
29+
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-200 rounded"
30+
title="Copy to clipboard"
31+
>
32+
{copied ? (
33+
<svg
34+
className="w-3 h-3 text-green-600"
35+
fill="none"
36+
viewBox="0 0 24 24"
37+
stroke="currentColor"
38+
>
39+
<path
40+
strokeLinecap="round"
41+
strokeLinejoin="round"
42+
strokeWidth="2"
43+
d="M5 13l4 4L19 7"
44+
/>
45+
</svg>
46+
) : (
47+
<svg
48+
className="w-3 h-3 text-gray-600"
49+
fill="none"
50+
viewBox="0 0 24 24"
51+
stroke="currentColor"
52+
>
53+
<path
54+
strokeLinecap="round"
55+
strokeLinejoin="round"
56+
strokeWidth="2"
57+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
58+
/>
59+
</svg>
60+
)}
61+
</button>
62+
</div>
63+
</div>
64+
);
65+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useState, useCallback } from 'react';
5+
6+
export function useCopy() {
7+
const [copied, setCopied] = useState(false);
8+
9+
const copy = useCallback(async (text: string) => {
10+
try {
11+
await navigator.clipboard.writeText(text);
12+
setCopied(true);
13+
// Reset after 2 seconds
14+
setTimeout(() => setCopied(false), 2000);
15+
return true;
16+
} catch (error) {
17+
console.error('Failed to copy text:', error);
18+
return false;
19+
}
20+
}, []);
21+
22+
return { copy, copied };
23+
}

packages/wallet-sdk/examples/demo-app/src/wallet/components/signing/PolicyStatusMessage.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
// Copyright (c) Mysten Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import type { AutoApprovalIssue } from '@mysten/wallet-sdk';
5+
46
interface PolicyStatusMessageProps {
7+
settingsIssues?: AutoApprovalIssue[];
58
onEditPolicy: () => void;
69
onRemovePolicy: () => void;
710
}
811

9-
export function PolicyStatusMessage({ onEditPolicy, onRemovePolicy }: PolicyStatusMessageProps) {
12+
export function PolicyStatusMessage({
13+
settingsIssues,
14+
onEditPolicy,
15+
onRemovePolicy,
16+
}: PolicyStatusMessageProps) {
1017
// Blue box for renewable issues with both options
1118
return (
1219
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
@@ -29,11 +36,14 @@ export function PolicyStatusMessage({ onEditPolicy, onRemovePolicy }: PolicyStat
2936
</div>
3037
</div>
3138
<div className="flex-1">
32-
<h3 className="text-sm font-medium text-blue-800 mb-1">
33-
Policy settings do not allow this transaction (not enough budget, or operation not
34-
approved, etc., better ux needed here)
35-
</h3>
36-
{/* <p className="text-sm text-blue-700 mb-3">{reason}</p> */}
39+
<h3 className="text-sm font-medium text-blue-800 mb-1">Auto-approval not available</h3>
40+
{settingsIssues && settingsIssues.length > 0 && (
41+
<ul className="text-sm text-blue-700 mb-3">
42+
{settingsIssues.map((issue, i) => (
43+
<li key={i}>{issue.message}</li>
44+
))}
45+
</ul>
46+
)}
3747
<div className="flex flex-wrap gap-2">
3848
<button
3949
onClick={onEditPolicy}

packages/wallet-sdk/examples/demo-app/src/wallet/components/signing/SigningModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function SigningModal({
6060
!autoApprovalState.canAutoApprove && (
6161
<div className="p-4 border-b border-gray-100">
6262
<PolicyStatusMessage
63+
settingsIssues={autoApprovalState.settingsIssues}
6364
onEditPolicy={onEditPolicy}
6465
onRemovePolicy={handleRemovePolicy}
6566
/>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { AutoApprovalAnalysis, AutoApprovalIssue } from '@mysten/wallet-sdk';
5+
import type { AutoApprovalState } from '../../../hooks/useAutoApproval.js';
6+
7+
interface AutoApprovalTabProps {
8+
analysis: AutoApprovalAnalysis;
9+
autoApprovalState: AutoApprovalState;
10+
}
11+
12+
export function AutoApprovalTab({ analysis, autoApprovalState }: AutoApprovalTabProps) {
13+
const { results } = analysis;
14+
const { operationType } = results;
15+
const { matchesPolicy, canAutoApprove, policyIssues, settingsIssues, analysisIssues, manager } =
16+
autoApprovalState;
17+
18+
// Get policy and settings from manager
19+
const policy = manager?.getState().policy;
20+
const settings = manager?.getSettings();
21+
const operation = policy?.operations.find((op: { id: string }) => op.id === operationType);
22+
23+
return (
24+
<div className="space-y-6">
25+
{/* Status Overview */}
26+
<div
27+
className={`rounded-lg p-4 ${
28+
canAutoApprove
29+
? 'bg-green-50 border border-green-200'
30+
: matchesPolicy
31+
? 'bg-yellow-50 border border-yellow-200'
32+
: 'bg-red-50 border border-red-200'
33+
}`}
34+
>
35+
<h3 className="text-sm font-semibold mb-2">Auto-Approval Status</h3>
36+
<div className="text-sm">
37+
{canAutoApprove ? (
38+
<div className="text-green-700">
39+
<span className="font-medium">✓ Ready for auto-approval</span>
40+
<p className="mt-1 text-xs">This transaction can be automatically approved.</p>
41+
</div>
42+
) : matchesPolicy ? (
43+
<div className="text-yellow-700">
44+
<span className="font-medium">⚠ Policy matches but settings prevent approval</span>
45+
<p className="mt-1 text-xs">Adjust your settings to enable auto-approval.</p>
46+
</div>
47+
) : (
48+
<div className="text-red-700">
49+
<span className="font-medium">✗ Not eligible for auto-approval</span>
50+
<p className="mt-1 text-xs">
51+
This transaction doesn't match the application's policy.
52+
</p>
53+
</div>
54+
)}
55+
</div>
56+
</div>
57+
58+
{/* Issues */}
59+
{(policyIssues.length > 0 || settingsIssues.length > 0 || analysisIssues.length > 0) && (
60+
<div>
61+
<h3 className="text-sm font-semibold text-gray-900 mb-3">
62+
Issues Preventing Auto-Approval
63+
</h3>
64+
65+
{analysisIssues.length > 0 && (
66+
<div className="mb-3">
67+
<h4 className="text-xs font-medium text-gray-700 mb-1">Analysis Issues</h4>
68+
<ul className="space-y-1">
69+
{analysisIssues.map((issue: AutoApprovalIssue, index: number) => (
70+
<li key={index} className="text-xs text-red-600 flex items-start">
71+
<span className="mr-2"></span>
72+
<span>{issue.message}</span>
73+
</li>
74+
))}
75+
</ul>
76+
</div>
77+
)}
78+
79+
{policyIssues.length > 0 && (
80+
<div className="mb-3">
81+
<h4 className="text-xs font-medium text-gray-700 mb-1">Policy Issues</h4>
82+
<ul className="space-y-1">
83+
{policyIssues.map((issue: AutoApprovalIssue, index: number) => (
84+
<li key={index} className="text-xs text-orange-600 flex items-start">
85+
<span className="mr-2"></span>
86+
<span>{issue.message}</span>
87+
</li>
88+
))}
89+
</ul>
90+
</div>
91+
)}
92+
93+
{settingsIssues.length > 0 && (
94+
<div className="mb-3">
95+
<h4 className="text-xs font-medium text-gray-700 mb-1">Settings Issues</h4>
96+
<ul className="space-y-1">
97+
{settingsIssues.map((issue: AutoApprovalIssue, index: number) => (
98+
<li key={index} className="text-xs text-yellow-600 flex items-start">
99+
<span className="mr-2"></span>
100+
<span>{issue.message}</span>
101+
</li>
102+
))}
103+
</ul>
104+
</div>
105+
)}
106+
</div>
107+
)}
108+
109+
{/* Operation Details */}
110+
{operation && (
111+
<div>
112+
<h3 className="text-sm font-semibold text-gray-900 mb-3">Operation Details</h3>
113+
<div className="bg-white border border-gray-200 rounded-lg p-3">
114+
<div className="mb-3">
115+
<div className="text-xs text-gray-600">Operation ID</div>
116+
<div className="font-medium text-sm">{operation.id}</div>
117+
</div>
118+
<div className="mb-3">
119+
<div className="text-xs text-gray-600">Description</div>
120+
<div className="text-sm">{operation.description}</div>
121+
</div>
122+
{operation.permissions && (
123+
<div>
124+
<div className="text-xs text-gray-600 mb-2">Required Permissions</div>
125+
<div className="space-y-1">
126+
{operation.permissions.ownedObjects?.map(
127+
(perm: { objectType: string; accessLevel: string }, index: number) => (
128+
<div key={`owned-${index}`} className="text-xs bg-gray-50 rounded p-2">
129+
<span className="font-medium">Object: </span>
130+
{perm.objectType} ({perm.accessLevel})
131+
</div>
132+
),
133+
)}
134+
{operation.permissions.balances?.map(
135+
(perm: { coinType: string }, index: number) => (
136+
<div key={`balance-${index}`} className="text-xs bg-gray-50 rounded p-2">
137+
<span className="font-medium">Coin: </span>
138+
{perm.coinType}
139+
</div>
140+
),
141+
)}
142+
</div>
143+
</div>
144+
)}
145+
</div>
146+
</div>
147+
)}
148+
149+
{/* Current Settings */}
150+
{settings && (
151+
<div>
152+
<h3 className="text-sm font-semibold text-gray-900 mb-3">Current Settings</h3>
153+
<div className="bg-white border border-gray-200 rounded-lg p-3 space-y-2">
154+
<div className="flex justify-between text-xs">
155+
<span className="text-gray-600">Approved Operations</span>
156+
<span className="font-medium">
157+
{settings.approvedOperations.join(', ') || 'None'}
158+
</span>
159+
</div>
160+
<div className="flex justify-between text-xs">
161+
<span className="text-gray-600">Remaining Transactions</span>
162+
<span className="font-medium">{settings.remainingTransactions ?? 'Unlimited'}</span>
163+
</div>
164+
<div className="flex justify-between text-xs">
165+
<span className="text-gray-600">USD Budget</span>
166+
<span className="font-medium">
167+
{settings.usdBudget ? `$${settings.usdBudget}` : 'Unlimited'}
168+
</span>
169+
</div>
170+
<div className="flex justify-between text-xs">
171+
<span className="text-gray-600">Expires</span>
172+
<span className="font-medium">{new Date(settings.expiration).toLocaleString()}</span>
173+
</div>
174+
{Object.entries(settings.coinBudgets).length > 0 && (
175+
<div className="pt-2 border-t border-gray-100">
176+
<div className="text-xs text-gray-600 mb-1">Coin Budgets</div>
177+
{Object.entries(settings.coinBudgets).map(([coin, budget]) => (
178+
<div key={coin} className="flex justify-between text-xs">
179+
<span className="text-gray-500">{formatCoinType(coin)}</span>
180+
<span>{String(budget)}</span>
181+
</div>
182+
))}
183+
</div>
184+
)}
185+
</div>
186+
</div>
187+
)}
188+
189+
{/* No Policy */}
190+
{!policy && (
191+
<div className="text-center py-8 text-gray-500">
192+
<p className="text-sm">No auto-approval policy available for this application</p>
193+
</div>
194+
)}
195+
</div>
196+
);
197+
}
198+
199+
function formatCoinType(type: string): string {
200+
const parts = type.split('::');
201+
return parts[parts.length - 1];
202+
}

0 commit comments

Comments
 (0)