1"use strict";(self.webpackChunkapptokens=self.webpackChunkapptokens||[]).push([[4743],{2745:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>o});const t=JSON.parse('{"id":"integration-guide/limit-break-amm/orders/transfer-handlers/custom-transfer-handlers","title":"Custom Transfer Handlers","description":"This describes a pre-release version of LBAMM. Interfaces and behavior may change.","source":"@site/docs/integration-guide/limit-break-amm/06-orders/transfer-handlers/custom-transfer-handlers.md","sourceDirName":"integration-guide/limit-break-amm/06-orders/transfer-handlers","slug":"/integration-guide/limit-break-amm/orders/transfer-handlers/custom-transfer-handlers","permalink":"/docs/integration-guide/limit-break-amm/orders/transfer-handlers/custom-transfer-handlers","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Custom Transfer Handlers","sidebar_label":"Custom Transfer Handlers","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Transfer Handlers","permalink":"/docs/integration-guide/limit-break-amm/orders/transfer-handlers/"},"next":{"title":"Permit Transfer Handler","permalink":"/docs/integration-guide/limit-break-amm/orders/transfer-handlers/permit-transfer-handler"}}');var a=r(4848),i=r(8453);const s={title:"Custom Transfer Handlers",sidebar_label:"Custom Transfer Handlers",sidebar_position:2},l="Custom Transfer Handlers",d={},o=[{value:"Required interface",id:"required-interface",level:2},{value:"Call timing and callback flow",id:"call-timing-and-callback-flow",level:2},{value:"Relationship to token hooks",id:"relationship-to-token-hooks",level:2},{value:"<code>validateHandlerOrder</code> is handler-controlled",id:"validatehandlerorder-is-handler-controlled",level:3},{value:"Safety and correctness requirements",id:"safety-and-correctness-requirements",level:2},{value:"Restrict who can call <code>ammHandleTransfer</code>
1",id:"restrict-who-can-call-ammhandletransfer",level:3},{value:"Don\u2019t trust <code>transferExtraData</code>",id:"dont-trust-transferextradata",level:3},{value:"Settlement invariants",id:"settlement-invariants",level:3},{value:"Executor identity matters",id:"executor-identity-matters",level:3},{value:"Handle non-standard ERC-20 behavior",id:"handle-non-standard-erc-20-behavior",level:3},{value:"Minimal integration checklist",id:"minimal-integration-checklist",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(n.admonition,{title:"Pre-Release Documentation",type:"warning",children:[(0,a.jsx)(n.p,{children:"This describes a pre-release version of LBAMM. Interfaces and behavior may change."}),(0,a.jsx)(n.p,{children:"Verify the exact repository commit before building production integrations."})]}),"\n",(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"custom-transfer-handlers",children:"Custom Transfer Handlers"})}),"\n",(0,a.jsxs)(n.p,{children:["Custom transfer handlers let developers extend ",(0,a.jsx)(n.strong,{children:"how swap input tokens are supplied"})," to LBAMM during swap finalization."]}),"\n",(0,a.jsxs)(n.p,{children:["A transfer handler is ",(0,a.jsx)(n.em,{children:"not"})," a router. It is a settlement module invoked by the AMM core to obtain the swap\u2019s input tokens, while receiving full context about the swap that just executed."]}),"\n",(0,a.jsxs)(n.p,{children:["This page describes the interface, the ",(0,a.jsx)(n.code,{children:"transferData"})," encoding used by LBAMM, callback behavior, and the most important safety constraints for building handlers that are compatible with hooks and integrators."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"required-interface",children:"Required interface"}),"\n",(0,a.jsxs)(n.p,{children:["Custom handlers ",(0,a.jsx)(n.em,{children:"must"})," implement ",(0,a.jsx)(n.a,{href:"https://github.com/limitbreakinc/lbamm-core/blob/main/src/interfaces/ILimitBreakAMMTransferHandler.sol",children:"ILimitBreakAMMTransferHandler"}),"."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"call-timing-and-callback-flow",children:"Call timing and callback flow"}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.code,{children:"ammHandleTransfer"})," is called by the AMM core ",(0,a.jsx)(n.strong,{children:"during swap finalization"}),"."]}),"\n",(0,a.jsxs)(n.p,{children:["Handlers may return ",(0,a.jsx)(n.code,{children:"callbackData"}),". If non-empty, the AMM will execute that callback on the handler ",(0,a.jsx)(n.strong,{children:"after"})," the swap finalizes."]}),"\n",(0,a.jsx)(n.p,{children:"Key points:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"The callback is a normal call to the handler (not a delegatecall)."}),"\n",(0,a.jsxs)(n.li,{children:["The AMM does not (and cannot) enforce any handler-internal authorization checks; handlers should validate ",(0,a.jsx)(n.code,{children:"msg.sender == AMM"})," if they require that property."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"If your handler does not need a post-finalization callback, return empty bytes."}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"relationship-to-token-hooks",children:"Relationship to token hooks"}),"\n",(0,a.jsxs)(n.p,{children:["Token hooks can observe which handler is used via ",(0,a.jsx)(n.code,{children:"SwapContext.transferHandler"}),"."]}),"\n",(0,a.jsx)(n.p,{children:"Important implications:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Tokens may restrict which handlers they allow."}),"\n",(0,a.jsx)(n.li,{children:"If your handler changes which party is effectively \u201con the other side\u201d of settlement (e.g., masking the maker/taker), some tokens may require additional validation."}),"\n"]}),"\n",(0,a.jsxs)(n.h3,{id:"validatehandlerorder-is-handler-controlled",children:[(0,a.jsx)(n.code,{children:"validateHandlerOrder"})," is handler-controlled"]}),"\n",(0,a.jsx)(n.p,{children:"Token hooks define:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.code,{children:"val
1idateHandlerOrder(...)"})}),"\n"]}),"\n",(0,a.jsxs)(n.p,{children:["This function is ",(0,a.jsx)(n.strong,{children:"not called by the AMM core"}),". A custom handler may choose to call it (e.g., during order creation) if the token expects handler-level validation."]}),"\n",(0,a.jsx)(n.p,{children:"This decision is part of the handler\u2019s compatibility contract with token hooks and integrators."}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"safety-and-correctness-requirements",children:"Safety and correctness requirements"}),"\n",(0,a.jsxs)(n.h3,{id:"restrict-who-can-call-ammhandletransfer",children:["Restrict who can call ",(0,a.jsx)(n.code,{children:"ammHandleTransfer"})]}),"\n",(0,a.jsx)(n.p,{children:"Handlers should generally require:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.code,{children:"msg.sender == AMM"})}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"The AMM cannot enforce handler-internal access control; this is part of the handler\u2019s responsibility."}),"\n",(0,a.jsxs)(n.h3,{id:"dont-trust-transferextradata",children:["Don\u2019t trust ",(0,a.jsx)(n.code,{children:"transferExtraData"})]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Validate length and decoding"}),"\n",(0,a.jsx)(n.li,{children:"Validate any embedded addresses (e.g., recipient / maker)"}),"\n",(0,a.jsx)(n.li,{children:"Validate any slippage bounds encoded in extra data"}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"settlement-invariants",children:"Settlement invariants"}),"\n",(0,a.jsx)(n.p,{children:"At minimum, the handler must ensure:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["the AMM receives the required ",(0,a.jsx)(n.code,{children:"amountIn"})," of ",(0,a.jsx)(n.code,{children:"swapOrder.tokenIn"})," by the end of handler execution"]}),"\n",(0,a.jsx)(n.li,{children:"no party can cause the handler to overpay relative to its authorization model"}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"executor-identity-matters",children:"Executor identity matters"}),"\n",(0,a.jsxs)(n.p,{children:["LBAMM treats ",(0,a.jsx)(n.code,{children:"executor"})," as the authoritative identity for the swap. If your handler introduces a separate authorization layer (permits, signatures, whitelists), bind it to ",(0,a.jsx)(n.code,{children:"executor"})," explicitly if required."]}),"\n",(0,a.jsx)(n.h3,{id:"handle-non-standard-erc-20-behavior",children:"Handle non-standard ERC-20 behavior"}),"\n",(0,a.jsx)(n.p,{children:"If your handler maintains internal balances, be aware that non-standard tokens (rebasing, fee-on-transfer, external balance adjustments) can cause drift between internal accounting and onchain balances."}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"minimal-integration-checklist",children:"Minimal integration checklist"}),"\n",(0,a.jsx)(n.p,{children:"When shipping a custom transfer handler, make sure you can answer:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["What bytes format does my handler expect in ",(0,a.jsx)(n.code,{children:"transferExtraData"}),"?"]}),"\n",(0,a.jsxs)(n.li,{children:["Does my handler ever require ",(0,a.jsx)(n.code,{children:"swapOrder.recipient == address(this)"}),"?"]}),"\n",(0,a.jsx)(n.li,{children:"What authorization model is used (signature, deposit, allowlist, hook)?"}),"\n",(0,a.jsxs)(n.li,{children:["How does it bind or validate ",(0,a.jsx)(n.code,{children:"executor"}),"?"]}),"\n",(0,a.jsx)(n.li,{children:"Does it need a post-finalization callback? If so, what does it do?"}),"\n",(0,a.jsxs)(n.li,{children:["Does it call token hook ",(0,a.jsx)(n.code,{children:"validateHandlerOrder"})," at any point? If yes, what is the schema of ",(0,a.jsx)(n.code,{children:"handlerOrderParams"}),"?"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>s,x:()=>l});var t=r(6540);const a={},i=t.createContext(a);function s(e){const n=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.