@@ -24,9 +24,96 @@ import {
2424 getQueuedTransactionHash ,
2525} from "../../../smart/lib/bundler.js" ;
2626import type { BundlerOptions } from "../../../smart/types.js" ;
27+ import { getDefaultBundlerUrl } from "../../../smart/lib/constants.js" ;
28+ import { getClientFetch } from "../../../../utils/fetch.js" ;
29+ import { stringify } from "../../../../utils/json.js" ;
30+
31+ interface DelegationContractResponse {
32+ id : string ;
33+ jsonrpc : string ;
34+ result : {
35+ delegationContract : string ;
36+ } ;
37+ }
38+
39+ // Cache for delegation contract address to avoid repeated requests
40+ let cachedDelegationContract : string | null = null ;
41+ let cachePromise : Promise < string > | null = null ;
42+
43+ /**
44+ * Fetches the delegation contract address from the bundler using the tw_getDelegationContract RPC method
45+ * @internal
46+ */
47+ async function getDelegationContractAddress ( args : {
48+ client : ThirdwebClient ;
49+ chain : Chain ;
50+ bundlerUrl ?: string ;
51+ } ) : Promise < string > {
52+ // Return cached result if available
53+ if ( cachedDelegationContract ) {
54+ return cachedDelegationContract ;
55+ }
56+
57+ // If there's already a request in progress, wait for it
58+ if ( cachePromise ) {
59+ return cachePromise ;
60+ }
61+
62+ // Create the promise and cache it
63+ cachePromise = ( async ( ) => {
64+ const { client, chain, bundlerUrl } = args ;
65+ const url = bundlerUrl ?? getDefaultBundlerUrl ( chain ) ;
66+ const fetchWithHeaders = getClientFetch ( client ) ;
67+
68+ const response = await fetchWithHeaders ( url , {
69+ useAuthToken : true ,
70+ body : stringify ( {
71+ id : 1 ,
72+ jsonrpc : "2.0" ,
73+ method : "tw_getDelegationContract" ,
74+ params : [ ] ,
75+ } ) ,
76+ headers : {
77+ "Content-Type" : "application/json" ,
78+ } ,
79+ method : "POST" ,
80+ } ) ;
81+
82+ if ( ! response . ok ) {
83+ throw new Error (
84+ `Failed to fetch delegation contract: ${ response . status } ${ response . statusText } ` ,
85+ ) ;
86+ }
87+
88+ const result : DelegationContractResponse = await response . json ( ) ;
89+
90+ if ( ( result as any ) . error ) {
91+ throw new Error (
92+ `Delegation contract RPC error: ${ JSON . stringify ( ( result as any ) . error ) } ` ,
93+ ) ;
94+ }
95+
96+ if ( ! result . result ?. delegationContract ) {
97+ throw new Error (
98+ "Invalid response: missing delegationContract in result" ,
99+ ) ;
100+ }
101+
102+ // Cache the result
103+ cachedDelegationContract = result . result . delegationContract ;
104+ return cachedDelegationContract ;
105+ } ) ( ) ;
106+
107+ try {
108+ const result = await cachePromise ;
109+ return result ;
110+ } finally {
111+ // Clear the promise cache after completion (success or failure)
112+ cachePromise = null ;
113+ }
114+ }
115+
27116
28- const MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS =
29- "0xD6999651Fc0964B9c6B444307a0ab20534a66560" ;
30117
31118export const create7702MinimalAccount = ( args : {
32119 client : ThirdwebClient ;
@@ -49,7 +136,11 @@ export const create7702MinimalAccount = (args: {
49136 } ) ;
50137 // check if account has been delegated already
51138 let authorization : SignedAuthorization | undefined ;
52- const isMinimalAccount = await is7702MinimalAccount ( eoaContract ) ;
139+ const delegationContractAddress = await getDelegationContractAddress ( {
140+ client,
141+ chain,
142+ } ) ;
143+ const isMinimalAccount = await is7702MinimalAccount ( eoaContract , delegationContractAddress ) ;
53144 if ( ! isMinimalAccount ) {
54145 // if not, sign authorization
55146 let nonce = firstTx . nonce
@@ -58,12 +149,12 @@ export const create7702MinimalAccount = (args: {
58149 await getNonce ( {
59150 client,
60151 address : adminAccount . address ,
61- chain : getCachedChain ( firstTx . chainId ) ,
152+ chain,
62153 } ) ,
63154 ) ;
64155 nonce += sponsorGas ? 0n : 1n ;
65156 const auth = await adminAccount . signAuthorization ?.( {
66- address : MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS ,
157+ address : getAddress ( delegationContractAddress ) ,
67158 chainId : firstTx . chainId ,
68159 nonce,
69160 } ) ;
@@ -131,7 +222,7 @@ export const create7702MinimalAccount = (args: {
131222 const executeTx = execute ( {
132223 calls : txs . map ( ( tx ) => ( {
133224 data : tx . data ?? "0x" ,
134- target : tx . to ?? "" ,
225+ target : getAddress ( tx . to ?? "" ) ,
135226 value : tx . value ?? 0n ,
136227 } ) ) ,
137228 contract : eoaContract ,
@@ -228,7 +319,7 @@ async function getNonce(args: {
228319 "../../../../rpc/actions/eth_getTransactionCount.js"
229320 ) . then ( ( { eth_getTransactionCount } ) =>
230321 eth_getTransactionCount ( rpcRequest , {
231- address,
322+ address : getAddress ( address ) ,
232323 blockTag : "pending" ,
233324 } ) ,
234325 ) ;
@@ -238,14 +329,14 @@ async function getNonce(args: {
238329async function is7702MinimalAccount (
239330 // biome-ignore lint/suspicious/noExplicitAny: TODO properly type tw contract
240331 eoaContract : ThirdwebContract < any > ,
332+ delegationContractAddress : string ,
241333) : Promise < boolean > {
242334 const code = await getBytecode ( eoaContract ) ;
243335 const isDelegated = code . length > 0 && code . startsWith ( "0xef0100" ) ;
244336 const target = `0x${ code . slice ( 8 , 48 ) } ` ;
245337 return (
246338 isDelegated &&
247- target . toLowerCase ( ) ===
248- MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS . toLowerCase ( )
339+ target . toLowerCase ( ) === delegationContractAddress . toLowerCase ( )
249340 ) ;
250341}
251342
0 commit comments