@@ -8,7 +8,11 @@ import {
88 makeCaveat ,
99} from './caveats.ts' ;
1010import { makeDelegation } from './delegation.ts' ;
11- import { ERC20_APPROVE_SELECTOR , ERC20_TRANSFER_SELECTOR } from './erc20.ts' ;
11+ import {
12+ ERC20_APPROVE_SELECTOR ,
13+ ERC20_TRANSFER_SELECTOR ,
14+ FIRST_ARG_OFFSET ,
15+ } from './erc20.ts' ;
1216import type {
1317 Address ,
1418 Caveat ,
@@ -19,12 +23,6 @@ import type {
1923
2024const harden = globalThis . harden ?? ( < T > ( value : T ) : T => value ) ;
2125
22- /**
23- * Byte offset of the first argument in ABI-encoded calldata (after the
24- * 4-byte function selector).
25- */
26- const FIRST_ARG_OFFSET = 4 ;
27-
2826/**
2927 * Encode an address as a 32-byte ABI-encoded word (left-padded with zeros).
3028 *
@@ -102,23 +100,47 @@ export function buildDelegationGrant(
102100 }
103101}
104102
103+ type Erc20GrantOptions = {
104+ methodName : 'transfer' | 'approve' ;
105+ selector : Hex ;
106+ delegator : Address ;
107+ delegate : Address ;
108+ token : Address ;
109+ max : bigint ;
110+ chainId : number ;
111+ validUntil ?: number ;
112+ restrictedAddress ?: Address ;
113+ entropy ?: Hex ;
114+ } ;
115+
105116/**
106- * Build a transfer delegation grant.
117+ * Build a delegation grant for an ERC-20 method (transfer or approve) .
107118 *
108- * @param options - Transfer grant options.
109- * @returns An unsigned DelegationGrant for ERC-20 transfers.
119+ * @param options - ERC-20 grant options.
120+ * @param options.methodName - The catalog method name ('transfer' or 'approve').
121+ * @param options.selector - The ERC-20 function selector.
122+ * @param options.delegator - The delegating account address.
123+ * @param options.delegate - The delegate account address.
124+ * @param options.token - The ERC-20 token contract address.
125+ * @param options.max - The maximum token amount allowed.
126+ * @param options.chainId - The chain ID.
127+ * @param options.validUntil - Optional Unix timestamp after which the delegation expires.
128+ * @param options.restrictedAddress - Optional address to lock the first argument to.
129+ * @param options.entropy - Optional entropy for delegation salt.
130+ * @returns An unsigned DelegationGrant for the given ERC-20 method.
110131 */
111- function buildTransferGrant ( options : TransferOptions ) : DelegationGrant {
112- const {
113- delegator,
114- delegate,
115- token,
116- max,
117- chainId,
118- validUntil,
119- recipient,
120- entropy,
121- } = options ;
132+ function buildErc20Grant ( {
133+ methodName,
134+ selector,
135+ delegator,
136+ delegate,
137+ token,
138+ max,
139+ chainId,
140+ validUntil,
141+ restrictedAddress,
142+ entropy,
143+ } : Erc20GrantOptions ) : DelegationGrant {
122144 const caveats : Caveat [ ] = [
123145 makeCaveat ( {
124146 type : 'allowedTargets' ,
@@ -127,7 +149,7 @@ function buildTransferGrant(options: TransferOptions): DelegationGrant {
127149 } ) ,
128150 makeCaveat ( {
129151 type : 'allowedMethods' ,
130- terms : encodeAllowedMethods ( [ ERC20_TRANSFER_SELECTOR ] ) ,
152+ terms : encodeAllowedMethods ( [ selector ] ) ,
131153 chainId,
132154 } ) ,
133155 makeCaveat ( {
@@ -139,8 +161,8 @@ function buildTransferGrant(options: TransferOptions): DelegationGrant {
139161
140162 const caveatSpecs : CaveatSpec [ ] = [ { type : 'cumulativeSpend' , token, max } ] ;
141163
142- if ( recipient !== undefined ) {
143- const value = abiEncodeAddress ( recipient ) ;
164+ if ( restrictedAddress !== undefined ) {
165+ const value = abiEncodeAddress ( restrictedAddress ) ;
144166 caveats . push (
145167 makeCaveat ( {
146168 type : 'allowedCalldata' ,
@@ -178,7 +200,22 @@ function buildTransferGrant(options: TransferOptions): DelegationGrant {
178200 entropy,
179201 } ) ;
180202
181- return harden ( { delegation, methodName : 'transfer' , caveatSpecs, token } ) ;
203+ return harden ( { delegation, methodName, caveatSpecs, token } ) ;
204+ }
205+
206+ /**
207+ * Build a transfer delegation grant.
208+ *
209+ * @param options - Transfer grant options.
210+ * @returns An unsigned DelegationGrant for ERC-20 transfers.
211+ */
212+ function buildTransferGrant ( options : TransferOptions ) : DelegationGrant {
213+ return buildErc20Grant ( {
214+ ...options ,
215+ methodName : 'transfer' ,
216+ selector : ERC20_TRANSFER_SELECTOR ,
217+ restrictedAddress : options . recipient ,
218+ } ) ;
182219}
183220
184221/**
@@ -188,76 +225,12 @@ function buildTransferGrant(options: TransferOptions): DelegationGrant {
188225 * @returns An unsigned DelegationGrant for ERC-20 approvals.
189226 */
190227function buildApproveGrant ( options : ApproveOptions ) : DelegationGrant {
191- const {
192- delegator,
193- delegate,
194- token,
195- max,
196- chainId,
197- validUntil,
198- spender,
199- entropy,
200- } = options ;
201- const caveats : Caveat [ ] = [
202- makeCaveat ( {
203- type : 'allowedTargets' ,
204- terms : encodeAllowedTargets ( [ token ] ) ,
205- chainId,
206- } ) ,
207- makeCaveat ( {
208- type : 'allowedMethods' ,
209- terms : encodeAllowedMethods ( [ ERC20_APPROVE_SELECTOR ] ) ,
210- chainId,
211- } ) ,
212- makeCaveat ( {
213- type : 'erc20TransferAmount' ,
214- terms : encodeErc20TransferAmount ( { token, amount : max } ) ,
215- chainId,
216- } ) ,
217- ] ;
218-
219- const caveatSpecs : CaveatSpec [ ] = [ { type : 'cumulativeSpend' , token, max } ] ;
220-
221- if ( spender !== undefined ) {
222- const value = abiEncodeAddress ( spender ) ;
223- caveats . push (
224- makeCaveat ( {
225- type : 'allowedCalldata' ,
226- terms : encodeAllowedCalldata ( { dataStart : FIRST_ARG_OFFSET , value } ) ,
227- chainId,
228- } ) ,
229- ) ;
230- caveatSpecs . push ( {
231- type : 'allowedCalldata' ,
232- dataStart : FIRST_ARG_OFFSET ,
233- value,
234- } ) ;
235- }
236-
237- if ( validUntil !== undefined ) {
238- caveats . push (
239- makeCaveat ( {
240- type : 'timestamp' ,
241- terms : encodeTimestamp ( { after : 0 , before : validUntil } ) ,
242- chainId,
243- } ) ,
244- ) ;
245- caveatSpecs . push ( {
246- type : 'blockWindow' ,
247- after : 0n ,
248- before : BigInt ( validUntil ) ,
249- } ) ;
250- }
251-
252- const delegation = makeDelegation ( {
253- delegator,
254- delegate,
255- caveats,
256- chainId,
257- entropy,
228+ return buildErc20Grant ( {
229+ ...options ,
230+ methodName : 'approve' ,
231+ selector : ERC20_APPROVE_SELECTOR ,
232+ restrictedAddress : options . spender ,
258233 } ) ;
259-
260- return harden ( { delegation, methodName : 'approve' , caveatSpecs, token } ) ;
261234}
262235
263236/**
0 commit comments