Skip to content

Commit 75e6005

Browse files
authored
Merge pull request #68 from MetaMask/refactor/tidy-utils-and-actions
Tidy up Utilities and Actions
2 parents ab65c46 + d9f03b2 commit 75e6005

13 files changed

Lines changed: 1542 additions & 104 deletions

packages/delegation-toolkit/src/actions/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/* eslint-disable */
22
// experimental actions will be moved here once they have stabilized
33

4-
// signDelegation action will be added here
5-
64
export {
75
// Individual action functions
86
getErc20PeriodTransferEnforcerAvailableAmount,
@@ -20,3 +18,18 @@ export {
2018
} from './getCaveatAvailableAmount';
2119

2220
export { isValid7702Implementation } from './isValid7702Implementation';
21+
22+
// Signing actions
23+
export {
24+
signDelegation,
25+
signDelegationActions,
26+
type SignDelegationParameters,
27+
type SignDelegationReturnType,
28+
} from './signDelegation';
29+
30+
export {
31+
signUserOperation,
32+
signUserOperationActions,
33+
type SignUserOperationParameters,
34+
type SignUserOperationReturnType,
35+
} from './signUserOperation';
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import type {
2+
Account,
3+
Address,
4+
Chain,
5+
Client,
6+
Transport,
7+
WalletClient,
8+
} from 'viem';
9+
import { BaseError } from 'viem';
10+
import { parseAccount } from 'viem/accounts';
11+
12+
import { prepareSignDelegationTypedData } from '../delegation';
13+
import type { Delegation } from '../types';
14+
15+
export type SignDelegationParameters = {
16+
/** Account to sign with */
17+
account?: Account | Address;
18+
/** The delegation to sign */
19+
delegation: Omit<Delegation, 'signature'>;
20+
/** The address of the delegation manager contract */
21+
delegationManager: Address;
22+
/** The chain ID for the signature */
23+
chainId: number;
24+
/** The name of the contract */
25+
name?: string;
26+
/** The version of the contract */
27+
version?: string;
28+
/** Whether to allow insecure unrestricted delegation */
29+
allowInsecureUnrestrictedDelegation?: boolean;
30+
};
31+
32+
export type SignDelegationReturnType = `0x${string}`;
33+
34+
/**
35+
* Signs a delegation using a wallet client.
36+
* @param client - The wallet client to use for signing.
37+
* @param parameters - The parameters for signing the delegation.
38+
* @returns The signature of the delegation.
39+
* @example
40+
* ```ts
41+
* const signature = await signDelegation(walletClient, {
42+
* delegation: {
43+
* delegate: '0x...',
44+
* delegator: '0x...',
45+
* authority: '0x...',
46+
* caveats: [],
47+
* salt: '0x'
48+
* },
49+
* delegationManager: '0x...',
50+
* chainId: 1
51+
* });
52+
* ```
53+
*/
54+
export async function signDelegation<
55+
TChain extends Chain | undefined,
56+
TAccount extends Account | undefined,
57+
>(
58+
client: Client<Transport, TChain, TAccount> & {
59+
signTypedData: WalletClient['signTypedData'];
60+
},
61+
parameters: SignDelegationParameters,
62+
): Promise<SignDelegationReturnType> {
63+
const {
64+
account: accountParam = client.account,
65+
delegation,
66+
delegationManager,
67+
chainId,
68+
name = 'DelegationManager',
69+
version = '1',
70+
allowInsecureUnrestrictedDelegation = false,
71+
} = parameters;
72+
73+
if (!accountParam) {
74+
throw new BaseError('Account not found. Please provide an account.');
75+
}
76+
77+
const account = parseAccount(accountParam);
78+
79+
const typedData = prepareSignDelegationTypedData({
80+
delegation,
81+
delegationManager,
82+
chainId,
83+
name,
84+
version,
85+
allowInsecureUnrestrictedDelegation,
86+
});
87+
88+
return client.signTypedData({
89+
account,
90+
...typedData,
91+
});
92+
}
93+
94+
/**
95+
* Creates a sign delegation action that can be used to extend a wallet client.
96+
* @returns A function that can be used with wallet client extend method.
97+
* @example
98+
* ```ts
99+
* const walletClient = createWalletClient({
100+
* chain: mainnet,
101+
* transport: http()
102+
* }).extend(signDelegationActions());
103+
* ```
104+
*/
105+
export function signDelegationActions() {
106+
return <
107+
TChain extends Chain | undefined,
108+
TAccount extends Account | undefined,
109+
>(
110+
client: Client<Transport, TChain, TAccount> & {
111+
signTypedData: WalletClient['signTypedData'];
112+
},
113+
) => ({
114+
signDelegation: async (
115+
parameters: Omit<SignDelegationParameters, 'chainId'> & {
116+
chainId?: number;
117+
},
118+
) =>
119+
signDelegation(client, {
120+
chainId:
121+
parameters.chainId ??
122+
(() => {
123+
if (!client.chain?.id) {
124+
throw new BaseError(
125+
'Chain ID is required. Either provide it in parameters or configure the client with a chain.',
126+
);
127+
}
128+
return client.chain.id;
129+
})(),
130+
...parameters,
131+
}),
132+
});
133+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type {
2+
Account,
3+
Address,
4+
Chain,
5+
Client,
6+
Transport,
7+
WalletClient,
8+
} from 'viem';
9+
import { BaseError } from 'viem';
10+
import { parseAccount } from 'viem/accounts';
11+
12+
import { prepareSignUserOperationTypedData } from '../userOp';
13+
import type { UserOperationV07 } from '../userOp';
14+
15+
export type SignUserOperationParameters = {
16+
/** Account to sign with */
17+
account?: Account | Address;
18+
/** The user operation to sign */
19+
userOperation: Omit<UserOperationV07, 'signature'>;
20+
/** The entry point contract address */
21+
entryPoint: { address: Address };
22+
/** The chain ID that the entry point is deployed on */
23+
chainId: number;
24+
/** The address of the smart account */
25+
address: Address;
26+
/** The name of the domain of the implementation contract */
27+
name: 'HybridDeleGator' | 'MultiSigDeleGator';
28+
/** The version of the domain of the implementation contract */
29+
version?: string;
30+
};
31+
32+
export type SignUserOperationReturnType = `0x${string}`;
33+
34+
/**
35+
* Signs a user operation using a wallet client.
36+
* @param client - The wallet client to use for signing.
37+
* @param parameters - The parameters for signing the user operation.
38+
* @returns The signature of the user operation.
39+
* @example
40+
* ```ts
41+
* const signature = await signUserOperation(walletClient, {
42+
* userOperation: {
43+
* sender: '0x...',
44+
* nonce: 0n,
45+
* callData: '0x',
46+
* callGasLimit: 1000000n,
47+
* verificationGasLimit: 1000000n,
48+
* preVerificationGas: 21000n,
49+
* maxFeePerGas: 1000000000n,
50+
* maxPriorityFeePerGas: 1000000000n
51+
* },
52+
* entryPoint: { address: '0x...' },
53+
* chainId: 1,
54+
* address: '0x...',
55+
* name: 'HybridDeleGator'
56+
* });
57+
* ```
58+
*/
59+
export async function signUserOperation<
60+
TChain extends Chain | undefined,
61+
TAccount extends Account | undefined,
62+
>(
63+
client: Client<Transport, TChain, TAccount> & {
64+
signTypedData: WalletClient['signTypedData'];
65+
},
66+
parameters: SignUserOperationParameters,
67+
): Promise<SignUserOperationReturnType> {
68+
const {
69+
account: accountParam = client.account,
70+
userOperation,
71+
entryPoint,
72+
chainId,
73+
name,
74+
address,
75+
version = '1',
76+
} = parameters;
77+
78+
if (!accountParam) {
79+
throw new BaseError('Account not found. Please provide an account.');
80+
}
81+
82+
const account = parseAccount(accountParam);
83+
84+
const typedData = prepareSignUserOperationTypedData({
85+
userOperation,
86+
entryPoint,
87+
chainId,
88+
name,
89+
address,
90+
version,
91+
});
92+
93+
return client.signTypedData({
94+
account,
95+
...typedData,
96+
});
97+
}
98+
99+
/**
100+
* Creates a sign user operation action that can be used to extend a wallet client.
101+
* @returns A function that can be used with wallet client extend method.
102+
* @example
103+
* ```ts
104+
* const walletClient = createWalletClient({
105+
* chain: mainnet,
106+
* transport: http()
107+
* }).extend(signUserOperationActions());
108+
* ```
109+
*/
110+
export function signUserOperationActions() {
111+
return <
112+
TChain extends Chain | undefined,
113+
TAccount extends Account | undefined,
114+
>(
115+
client: Client<Transport, TChain, TAccount> & {
116+
signTypedData: WalletClient['signTypedData'];
117+
},
118+
) => ({
119+
signUserOperation: async (
120+
parameters: Omit<SignUserOperationParameters, 'chainId'> & {
121+
chainId?: number;
122+
},
123+
) =>
124+
signUserOperation(client, {
125+
chainId:
126+
parameters.chainId ??
127+
(() => {
128+
if (!client.chain?.id) {
129+
throw new BaseError(
130+
'Chain ID is required. Either provide it in parameters or configure the client with a chain.',
131+
);
132+
}
133+
return client.chain.id;
134+
})(),
135+
...parameters,
136+
}),
137+
});
138+
}

0 commit comments

Comments
 (0)