@@ -64,8 +64,26 @@ func (c *BatchedEvmScheme) CreatePaymentPayloadWithExtensions(
6464 }
6565
6666 // Only Permit2 deposits gas-sponsor through these extensions; ERC-3009
67- // deposits authorize the transfer in the same signature.
68- if ! isPermit2Deposit (requirements , result ) {
67+ // deposits authorize the transfer in the same signature. Read
68+ // `assetTransferMethod` from requirements.Extra (TS source of truth)
69+ // and fall back to inspecting the deposit authorization shape so a
70+ // missing wire field still routes correctly.
71+ isPermit2 := false
72+ if requirements .Extra != nil {
73+ if v , ok := requirements .Extra ["assetTransferMethod" ].(string ); ok && v != "" {
74+ isPermit2 = v == "permit2"
75+ }
76+ }
77+ if ! isPermit2 && result .Payload != nil {
78+ if dep , ok := result .Payload ["deposit" ].(map [string ]interface {}); ok {
79+ if auth , ok := dep ["authorization" ].(map [string ]interface {}); ok {
80+ if _ , has := auth ["permit2Authorization" ]; has {
81+ isPermit2 = true
82+ }
83+ }
84+ }
85+ }
86+ if ! isPermit2 {
6987 return result , nil
7088 }
7189
@@ -81,28 +99,6 @@ func (c *BatchedEvmScheme) CreatePaymentPayloadWithExtensions(
8199 return result , nil
82100}
83101
84- // isPermit2Deposit reports whether the resolved deposit payload uses the
85- // Permit2 transfer method. Reads requirements.Extra first (TS source of
86- // truth) and falls back to inspecting the deposit authorization shape, so a
87- // missing `assetTransferMethod` field on the wire still routes correctly.
88- func isPermit2Deposit (requirements types.PaymentRequirements , payload types.PaymentPayload ) bool {
89- if requirements .Extra != nil {
90- if v , ok := requirements .Extra ["assetTransferMethod" ].(string ); ok && v != "" {
91- return v == "permit2"
92- }
93- }
94- if payload .Payload != nil {
95- if dep , ok := payload .Payload ["deposit" ].(map [string ]interface {}); ok {
96- if auth , ok := dep ["authorization" ].(map [string ]interface {}); ok {
97- if _ , has := auth ["permit2Authorization" ]; has {
98- return true
99- }
100- }
101- }
102- }
103- return false
104- }
105-
106102// trySignEip2612Permit attempts to sign an EIP-2612 permit authorizing Permit2
107103// to spend the deposit amount. Returns nil (no error) when the extension is
108104// not advertised, the token does not expose name/version, the signer cannot
@@ -147,8 +143,19 @@ func (c *BatchedEvmScheme) trySignEip2612Permit(
147143
148144 tokenAddress := evm .NormalizeAddress (requirements .Asset )
149145
150- depositAmount , ok := readDepositAmount (result )
151- if ! ok {
146+ // Pull `payload.deposit.amount` from the freshly-built batched deposit
147+ // payload. The signed EIP-2612 `info.amount` must equal the BATCH
148+ // deposit (what the facilitator's `validateBatchEip2612Permit` checks),
149+ // not `requirements.Amount` (the per-request charge).
150+ var depositAmount string
151+ if result .Payload != nil {
152+ if dep , ok := result .Payload ["deposit" ].(map [string ]interface {}); ok {
153+ if amt , ok := dep ["amount" ].(string ); ok && amt != "" {
154+ depositAmount = amt
155+ }
156+ }
157+ }
158+ if depositAmount == "" {
152159 return nil , nil
153160 }
154161
@@ -159,7 +166,22 @@ func (c *BatchedEvmScheme) trySignEip2612Permit(
159166 return nil , nil
160167 }
161168
162- deadline := readPermit2DeadlineFromBatchedDeposit (result )
169+ // Align EIP-2612 deadline with the just-signed Permit2 authorization
170+ // (`payload.deposit.authorization.permit2Authorization.deadline`) so
171+ // both signatures expire together. Falls back to
172+ // `now + maxTimeoutSeconds` when the field isn't present.
173+ deadline := ""
174+ if result .Payload != nil {
175+ if dep , ok := result .Payload ["deposit" ].(map [string ]interface {}); ok {
176+ if auth , ok := dep ["authorization" ].(map [string ]interface {}); ok {
177+ if permit2 , ok := auth ["permit2Authorization" ].(map [string ]interface {}); ok {
178+ if d , ok := permit2 ["deadline" ].(string ); ok {
179+ deadline = d
180+ }
181+ }
182+ }
183+ }
184+ }
163185 if deadline == "" {
164186 deadline = fmt .Sprintf ("%d" , time .Now ().Unix ()+ int64 (requirements .MaxTimeoutSeconds ))
165187 }
@@ -214,14 +236,12 @@ func (c *BatchedEvmScheme) trySignErc20Approval(
214236 tokenAddress := evm .NormalizeAddress (requirements .Asset )
215237
216238 if readSigner , hasRead := c .signer .(evm.ClientEvmSignerWithReadContract ); hasRead {
217- // Use deposit amount, not requirements.Amount, since the deposit is
218- // what the facilitator will pull through Permit2 — that's what needs
219- // to fit under the existing allowance.
220- needed := requirements .Amount
221- if v , ok := readDepositAmountFromAnyPayload (c , requirements ); ok {
222- needed = v
223- }
224- if hasSufficientPermit2Allowance (ctx , readSigner , tokenAddress , c .signer .Address (), needed ) {
239+ // Approximate the deposit amount with the per-request amount —
240+ // sufficient for the allowance short-circuit since `deposit ≥
241+ // requirements.Amount` always holds for batched flows. Skip the
242+ // short-circuit when no per-request amount is available.
243+ if requirements .Amount != "" &&
244+ hasSufficientPermit2Allowance (ctx , readSigner , tokenAddress , c .signer .Address (), requirements .Amount ) {
225245 return nil , nil
226246 }
227247 }
@@ -238,59 +258,6 @@ func (c *BatchedEvmScheme) trySignErc20Approval(
238258 }, nil
239259}
240260
241- // readDepositAmount pulls `payload.deposit.amount` from a freshly built
242- // batched deposit payload map. Returns ("", false) for non-deposit payloads.
243- func readDepositAmount (payload types.PaymentPayload ) (string , bool ) {
244- if payload .Payload == nil {
245- return "" , false
246- }
247- dep , ok := payload .Payload ["deposit" ].(map [string ]interface {})
248- if ! ok {
249- return "" , false
250- }
251- amt , ok := dep ["amount" ].(string )
252- if ! ok || amt == "" {
253- return "" , false
254- }
255- return amt , true
256- }
257-
258- // readDepositAmountFromAnyPayload is a fallback for callers that don't have
259- // the just-built payload in hand (e.g. `trySignErc20Approval` running before
260- // the deposit payload has been re-resolved). Approximates with the per-request
261- // amount — sufficient for the allowance short-circuit since deposit ≥ amount
262- // always holds for batched flows.
263- func readDepositAmountFromAnyPayload (_ * BatchedEvmScheme , requirements types.PaymentRequirements ) (string , bool ) {
264- if requirements .Amount == "" {
265- return "" , false
266- }
267- return requirements .Amount , true
268- }
269-
270- // readPermit2DeadlineFromBatchedDeposit extracts the deadline from the batched
271- // deposit's Permit2 authorization map: payload.deposit.authorization.permit2Authorization.deadline.
272- // Mirrors the TS scheme which aligns the EIP-2612 deadline with the Permit2
273- // deadline so both expire together.
274- func readPermit2DeadlineFromBatchedDeposit (payload types.PaymentPayload ) string {
275- if payload .Payload == nil {
276- return ""
277- }
278- dep , ok := payload .Payload ["deposit" ].(map [string ]interface {})
279- if ! ok {
280- return ""
281- }
282- auth , ok := dep ["authorization" ].(map [string ]interface {})
283- if ! ok {
284- return ""
285- }
286- permit2 , ok := auth ["permit2Authorization" ].(map [string ]interface {})
287- if ! ok {
288- return ""
289- }
290- d , _ := permit2 ["deadline" ].(string )
291- return d
292- }
293-
294261// hasSufficientPermit2Allowance returns true when `owner` has already
295262// approved at least `requiredAmount` to Permit2 for `tokenAddress`. Returns
296263// false on any RPC error so we conservatively sign an extension rather than
0 commit comments