Skip to content

Commit dbfbcb9

Browse files
authored
Merge branch 'main' into iyk/investigate-rn-crypto
2 parents 7d583fe + c3616c3 commit dbfbcb9

File tree

7 files changed

+499
-18
lines changed

7 files changed

+499
-18
lines changed

aa-sdk/core/src/actions/smartAccount/internal/initUserOperation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export async function _initUserOperation<
6262

6363
const signature = account.getDummySignature();
6464

65-
const nonce = account.getAccountNonce(overrides?.nonceKey);
65+
const nonce =
66+
overrides?.nonce ?? account.getAccountNonce(overrides?.nonceKey);
6667

6768
const struct =
6869
entryPoint.version === "0.6.0"

aa-sdk/core/src/types.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,6 @@ export type UserOperationOverrides<
113113
verificationGasLimit:
114114
| UserOperationStruct<TEntryPointVersion>["verificationGasLimit"]
115115
| Multiplier;
116-
/**
117-
* This can be used to override the key used when calling `entryPoint.getNonce`
118-
* It is useful when you want to use parallel nonces for user operations
119-
*
120-
* NOTE: not all bundlers fully support this feature and it could be that your bundler will still only include
121-
* one user operation for your account in a bundle
122-
*/
123-
nonceKey: bigint;
124116

125117
/**
126118
* The same state overrides for
@@ -132,7 +124,21 @@ export type UserOperationOverrides<
132124
*/
133125
stateOverride: StateOverride;
134126
} & UserOperationPaymasterOverrides<TEntryPointVersion>
135-
>;
127+
> &
128+
/**
129+
* This can be used to override the nonce or nonce key used when calling `entryPoint.getNonce`
130+
* It is useful when you want to use parallel nonces for user operations
131+
*
132+
* NOTE: not all bundlers fully support this feature and it could be that your bundler will still only include
133+
* one user operation for your account in a bundle
134+
*/
135+
Partial<
136+
| {
137+
nonceKey: bigint;
138+
nonce: never;
139+
}
140+
| { nonceKey: never; nonce: bigint }
141+
>;
136142
// [!endregion UserOperationOverrides]
137143

138144
// [!region UserOperationRequest_v6]

account-kit/smart-contracts/src/ma-v2/account/nativeSMASigner.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import {
88
type TypedDataDefinition,
99
type Chain,
1010
type Address,
11+
concat,
1112
} from "viem";
1213

1314
import {
1415
packUOSignature,
1516
pack1271Signature,
1617
DEFAULT_OWNER_ENTITY_ID,
1718
} from "../utils.js";
19+
import { SignatureType } from "../modules/utils.js";
1820
/**
1921
* Creates an object with methods for generating a dummy signature, signing user operation hashes, signing messages, and signing typed data.
2022
*
@@ -99,7 +101,10 @@ export const nativeSMASigner = (
99101
typedDataDefinition?.domain?.verifyingContract === accountAddress;
100102

101103
return isDeferredAction
102-
? signer.signTypedData(typedDataDefinition)
104+
? concat([
105+
SignatureType.EOA,
106+
await signer.signTypedData(typedDataDefinition),
107+
])
103108
: pack1271Signature({
104109
validationSignature: await signer.signTypedData({
105110
domain: {
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import {
2+
AccountNotFoundError,
3+
InvalidNonceKeyError,
4+
EntryPointNotFoundError,
5+
type UserOperationCallData,
6+
type BatchUserOperationCallData,
7+
type UserOperationRequest_v7,
8+
} from "@aa-sdk/core";
9+
import {
10+
type Address,
11+
type Hex,
12+
concatHex,
13+
maxUint152,
14+
getContract,
15+
encodePacked,
16+
size,
17+
toHex,
18+
} from "viem";
19+
import type { ModularAccountV2Client } from "../client/client.js";
20+
21+
export type DeferredActionTypedData = {
22+
domain: {
23+
chainId: number;
24+
verifyingContract: Address;
25+
};
26+
types: {
27+
DeferredAction: [
28+
{ name: "nonce"; type: "uint256" },
29+
{ name: "deadline"; type: "uint48" },
30+
{ name: "call"; type: "bytes" }
31+
];
32+
};
33+
primaryType: "DeferredAction";
34+
message: {
35+
nonce: bigint;
36+
deadline: number;
37+
call: Hex;
38+
};
39+
};
40+
41+
export type DeferredActionReturnData = {
42+
typedData: DeferredActionTypedData;
43+
nonceOverride: bigint;
44+
};
45+
46+
export type CreateDeferredActionTypedDataParams = {
47+
callData: Hex;
48+
deadline: number;
49+
entityId: number;
50+
isGlobalValidation: boolean;
51+
nonceKeyOverride?: bigint;
52+
};
53+
54+
export type BuildDeferredActionDigestParams = {
55+
typedData: DeferredActionTypedData;
56+
sig: Hex;
57+
};
58+
59+
export type BuildUserOperationWithDeferredActionParams = {
60+
uo: UserOperationCallData | BatchUserOperationCallData;
61+
signaturePrepend: Hex;
62+
nonceOverride: bigint;
63+
};
64+
65+
export type DeferralActions = {
66+
createDeferredActionTypedDataObject: (
67+
args: CreateDeferredActionTypedDataParams
68+
) => Promise<DeferredActionReturnData>;
69+
buildDeferredActionDigest: (args: BuildDeferredActionDigestParams) => Hex;
70+
buildUserOperationWithDeferredAction: (
71+
args: BuildUserOperationWithDeferredActionParams
72+
) => Promise<UserOperationRequest_v7>;
73+
};
74+
75+
/**
76+
* Provides deferred action functionalities for a MA v2 client, ensuring compatibility with `SmartAccountClient`.
77+
*
78+
* @param {ModularAccountV2Client} client - The client instance which provides account and sendUserOperation functionality.
79+
* @returns {object} - An object containing three methods: `createDeferredActionTypedDataObject`, `buildDeferredActionDigest`, and `buildUserOperationWithDeferredAction`.
80+
*/
81+
export const deferralActions: (
82+
client: ModularAccountV2Client
83+
) => DeferralActions = (client: ModularAccountV2Client): DeferralActions => {
84+
const createDeferredActionTypedDataObject = async ({
85+
callData,
86+
deadline,
87+
entityId,
88+
isGlobalValidation,
89+
nonceKeyOverride,
90+
}: CreateDeferredActionTypedDataParams): Promise<DeferredActionReturnData> => {
91+
if (!client.account) {
92+
throw new AccountNotFoundError();
93+
}
94+
95+
const baseNonceKey = nonceKeyOverride || 0n;
96+
if (baseNonceKey > maxUint152) {
97+
throw new InvalidNonceKeyError(baseNonceKey);
98+
}
99+
100+
const entryPoint = client.account.getEntryPoint();
101+
if (entryPoint === undefined) {
102+
throw new EntryPointNotFoundError(client.chain, "0.7.0");
103+
}
104+
105+
const entryPointContract = getContract({
106+
address: entryPoint.address,
107+
abi: entryPoint.abi,
108+
client: client,
109+
});
110+
111+
// 2 = deferred action flags 0b10
112+
// 1 = isGlobal validation flag 0b01
113+
const fullNonceKey: bigint =
114+
((baseNonceKey << 40n) + (BigInt(entityId) << 8n)) |
115+
2n |
116+
(isGlobalValidation ? 1n : 0n);
117+
118+
const nonceOverride = (await entryPointContract.read.getNonce([
119+
client.account.address,
120+
fullNonceKey,
121+
])) as bigint;
122+
123+
return {
124+
typedData: {
125+
domain: {
126+
chainId: await client.getChainId(),
127+
verifyingContract: client.account.address,
128+
},
129+
types: {
130+
DeferredAction: [
131+
{ name: "nonce", type: "uint256" },
132+
{ name: "deadline", type: "uint48" },
133+
{ name: "call", type: "bytes" },
134+
],
135+
},
136+
primaryType: "DeferredAction",
137+
message: {
138+
nonce: nonceOverride,
139+
deadline: deadline,
140+
call: callData,
141+
},
142+
},
143+
nonceOverride: nonceOverride,
144+
};
145+
};
146+
147+
/**
148+
* Creates the digest which must be prepended to the userOp signature.
149+
*
150+
* Assumption: The client this extends is used to sign the typed data.
151+
*
152+
* @param {object} args The argument object containing the following:
153+
* @param {DeferredActionTypedData} args.typedData The typed data object for the deferred action
154+
* @param {Hex} args.sig The signature to include in the digest
155+
* @returns {Hex} The encoded digest to be prepended to the userOp signature
156+
*/
157+
const buildDeferredActionDigest = ({
158+
typedData,
159+
sig,
160+
}: BuildDeferredActionDigestParams): Hex => {
161+
const signerEntity = client.account.signerEntity;
162+
const validationLocator =
163+
(BigInt(signerEntity.entityId) << 8n) |
164+
(signerEntity.isGlobalValidation ? 1n : 0n);
165+
166+
let encodedData = encodePacked(
167+
["uint168", "uint48", "bytes"],
168+
[validationLocator, typedData.message.deadline, typedData.message.call]
169+
);
170+
171+
const encodedDataLength = size(encodedData);
172+
const sigLength = size(sig);
173+
174+
encodedData = concatHex([
175+
toHex(encodedDataLength, { size: 4 }),
176+
encodedData,
177+
toHex(sigLength, { size: 4 }),
178+
sig,
179+
]);
180+
181+
return encodedData;
182+
};
183+
184+
/**
185+
* Builds a user operation with a deferred action by wrapping buildUserOperation() with a dummy signature override.
186+
*
187+
* @param {object} args The argument object containing the following:
188+
* @param {UserOperationCallData | BatchUserOperationCallData} args.uo The user operation call data to build
189+
* @param {Hex} args.signaturePrepend The signature data to prepend to the dummy signature
190+
* @param {bigint} args.nonceOverride The nonce to override in the user operation, generally given from the typed data builder
191+
* @returns {Promise<UserOperationRequest_v7>} The unsigned user operation request with the deferred action
192+
*/
193+
const buildUserOperationWithDeferredAction = async ({
194+
uo,
195+
signaturePrepend,
196+
nonceOverride,
197+
}: BuildUserOperationWithDeferredActionParams): Promise<UserOperationRequest_v7> => {
198+
// Check if client.account is defined
199+
if (client.account === undefined) {
200+
throw new AccountNotFoundError();
201+
}
202+
203+
// Pre-fetch the dummy sig so we can override `client.account.getDummySignature()`
204+
const dummySig = await client.account.getDummySignature();
205+
206+
// Cache the previous dummy signature getter
207+
const previousDummySigGetter = client.account.getDummySignature;
208+
209+
// Override client.account.getDummySignature() so `client.buildUserOperation()` uses the prepended hex and the dummy signature during gas estimation
210+
client.account.getDummySignature = () => {
211+
return concatHex([signaturePrepend, dummySig as Hex]);
212+
};
213+
214+
const unsignedUo = (await client.buildUserOperation({
215+
uo: uo,
216+
overrides: {
217+
nonce: nonceOverride,
218+
},
219+
})) as UserOperationRequest_v7;
220+
221+
// Restore the dummy signature getter
222+
client.account.getDummySignature = previousDummySigGetter;
223+
224+
return unsignedUo;
225+
};
226+
227+
return {
228+
createDeferredActionTypedDataObject,
229+
buildDeferredActionDigest,
230+
buildUserOperationWithDeferredAction,
231+
};
232+
};

0 commit comments

Comments
 (0)