@manifoldxyz/dropkit 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/index.cjs","../src/react/useMint.ts","../src/core/relay/client.ts","../src/core/relay/status.ts","../src/core/relay/outstanding.ts","../src/core/adapters/contract/index.ts","../src/react/PayWithPicker.ts","../src/react/MintButton.ts","../src/index.ts"],"names":["formatUnits","getAddress","wallet","hash","receipt","readDrop","useState","useCallback","createElement"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;AC7BA,8BAAqE;AACrE,4BAAuG;AD+BvG;AACA;AEjCA;AAKO,IAAM,UAAA,EAAY,EAAE,OAAA,EAAS,wBAAA,EAA0B,OAAA,EAAS,kCAAkC,CAAA;AAGzG,IAAM,mBAAA,EAAqB,CAAC,CAAA,EAAG,IAAA,EAAM,EAAA,EAAI,GAAA,EAAK,KAAK,CAAA;AACnD,IAAM,oBAAA,EAAyC,CAAC,CAAA,EAAG,IAAA,EAAM,EAAA,EAAI,GAAG,CAAA;AAChE,IAAM,QAAA,EAAU,QAAA;AAChB,IAAM,aAAA,EAAe,KAAA;AAEd,IAAM,eAAA,EAAiB,CAAC,WAAA,EAAA,GAAiC,YAAA,IAAgB,OAAA;AAGzE,SAAS,YAAA,CAAa,WAAA,EAA+B;AAC1D,EAAA,GAAA,CAAI,mBAAA,CAAoB,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,CAAC,GAAG,kBAAkB,CAAA;AAC5E,EAAA,GAAA,CAAI,YAAA,IAAgB,OAAA,EAAS,OAAO,CAAC,OAAA,EAAS,YAAY,CAAA;AAC1D,EAAA,OAAO,CAAC,CAAA;AACV;AA2BA,IAAM,SAAA,EAAW,GAAA;AACjB,IAAM,MAAA,kBAAQ,IAAI,GAAA,CAAwD,CAAA;AAE1E,IAAM,SAAA,EAAW,CAAC,CAAA,EAAA,GAChB,CAAC,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,aAAA,EAAe,CAAA,CAAE,kBAAA,EAAoB,CAAA,CAAE,EAAA,CAAG,EAAA,CAAG,WAAA,CAAY,CAAA,EAAG,CAAA,CAAE,EAAA,CAAG,KAAA,EAAO,6BAAA,CAAU,CAAE,EAAA,CAAG,IAAI,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,WAAA,CAAY,CAAA,EAAG,CAAA,CAAE,SAAA,CAAU,WAAA,CAAY,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAOxK,IAAM,IAAA,EAAM,CAAC,CAAA,EAAA,GAAuB;AAClC,EAAA,GAAA,CAAI,OAAO,EAAA,IAAM,SAAA,GAAY,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,MAAA,CAAO,CAAC,CAAA;AAC7D,EAAA,GAAA,CAAI,OAAO,EAAA,IAAM,SAAA,GAAY,MAAA,CAAO,aAAA,CAAc,CAAC,EAAA,GAAK,EAAA,GAAK,CAAA,EAAG,OAAO,MAAA,CAAO,CAAC,CAAA;AAC/E,EAAA,OAAO,EAAA;AACT,CAAA;AAGA,IAAM,IAAA,EAAM,CAAC,CAAA,EAAA,GAAsB,EAAA,IAAM,KAAA,GAAQ,OAAO,EAAA,IAAM,SAAA,EAAY,EAAA,EAAa,CAAC,CAAA;AAExF,SAAS,OAAA,CAAQ,MAAA,EAAiB,QAAA,EAA0B;AAC1D,EAAA,MAAM,EAAA,EAAI,GAAA,CAAI,QAAQ,CAAA;AACtB,EAAA,MAAM,SAAA,EAAW,OAAO,CAAA,CAAE,SAAA,IAAa,SAAA,EAAW,CAAA,CAAE,SAAA,EAAW,EAAA;AAC/D,EAAA,MAAM,OAAA,EAAS,OAAO,CAAA,CAAE,OAAA,IAAW,SAAA,EAAW,CAAA,CAAE,OAAA,EAAS,KAAA;AACzD,EAAA,MAAM,MAAA,EAAQ,GAAA,CAAI,MAAM,CAAA;AACxB,EAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,MAAA,EAAQ,SAAA,EAAW,+BAAA,KAAY,EAAO,QAAQ,EAAE,CAAA;AAC5E;AAGA,SAAS,YAAA,CAAa,SAAA,EAA4B;AAChD,EAAA,GAAA,CAAI,OAAO,UAAA,IAAc,QAAA,EAAU,OAAO,EAAA;AAC1C,EAAA,MAAM,KAAA,EAAO,SAAA,CAAU,OAAA,CAAQ,2BAAA,EAA6B,EAAE,CAAA,CAAE,IAAA,CAAK,CAAA;AACrE,EAAA,OAAO,KAAA,IAAS,qBAAA,EAAuB,GAAA,EAAK,IAAA;AAC9C;AAGO,SAAS,UAAA,CAAW,IAAA,EAAe,aAAA,EAAmC;AAC3E,EAAA,MAAM,EAAA,EAAI,GAAA,CAAI,IAAI,CAAA;AAClB,EAAA,MAAM,MAAA,EAAQ,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,KAAK,EAAA,EAAI,CAAA,CAAE,KAAA,CAAM,GAAA,CAAI,GAAG,EAAA,EAAI,CAAC,CAAA;AAC3D,EAAA,MAAM,YAAA,EAAc,CAAA,EAAA,GAAM,IAAI,gCAAA,CAAU,oBAAA,EAAsB,oDAAoD,CAAA;AAClH,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,EAAA,GAAK,KAAA,CAAM,CAAC,CAAA,CAAG,KAAA,IAAS,aAAA,EAAe,MAAM,WAAA,CAAY,CAAA;AAC9E,EAAA,MAAM,KAAA,EAAO,KAAA,CAAM,CAAC,CAAA;AACpB,EAAA,MAAM,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,KAAK,EAAA,EAAI,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,EAAA,EAAI,CAAC,CAAA;AAEpE,EAAA,GAAA,CAAI,QAAA,CAAS,OAAA,IAAW,CAAA,EAAG,MAAM,WAAA,CAAY,CAAA;AAC7C,EAAA,MAAM,MAAA,EAAQ,QAAA,CAAS,GAAA,CAAI,CAAC,IAAA,EAAA,GAAsB;AAChD,IAAA,MAAM,EAAA,EAAI,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA;AACvB,IAAA,MAAM,QAAA,EAAU,MAAA,CAAO,CAAA,CAAE,OAAO,CAAA;AAChC,IAAA,GAAA,CAAI,QAAA,IAAY,cAAA,GAAiB,OAAO,CAAA,CAAE,GAAA,IAAO,SAAA,GAAY,CAAC,yBAAA,CAAM,CAAE,IAAI,CAAA,EAAG,MAAM,WAAA,CAAY,CAAA;AAC/F,IAAA,OAAO,EAAE,OAAA,EAAS,EAAA,EAAI,8BAAA,CAAW,CAAE,EAAE,CAAA,EAAG,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,KAAA,EAAO,GAAA,CAAI,CAAA,CAAE,KAAK,CAAA,EAAG,GAAA,EAAK,CAAA,CAAE,IAAA,IAAQ,KAAA,EAAA,EAAY,KAAA,EAAO,GAAA,CAAI,CAAA,CAAE,GAAG,EAAE,CAAA;AAAA,EAC1H,CAAC,CAAA;AACD,EAAA,MAAM,UAAA,EAAa,yBAAA,IAAM,CAAK,SAAS,EAAA,EAAI,IAAA,CAAK,UAAA,EAAY,yBAAA,CAAM,CAAE,SAAS,EAAA,EAAI,CAAA,CAAE,UAAA,EAAY,IAAA;AAC/F,EAAA,GAAA,CAAI,CAAC,SAAA,EAAW,MAAM,WAAA,CAAY,CAAA;AAClC,EAAA,MAAM,QAAA,EAAU,GAAA,CAAI,CAAA,CAAE,OAAO,CAAA;AAC7B,EAAA,MAAM,WAAA,EAAa,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAA;AACzC,EAAA,MAAM,KAAA,EAAO,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA;AACvB,EAAA,MAAM,SAAA,EAAW,OAAA,CAAQ,UAAA,CAAW,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAC/D,EAAA,GAAA,CAAI,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,CAAE,QAAA,IAAY,KAAA,EAAA,GAAa,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,CAAE,QAAA,IAAY,iBAAA,EAAa,MAAM,WAAA,CAAY,CAAA;AAE1H,EAAA,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA,CAAG,MAAA,IAAU,QAAA,CAAS,KAAA,EAAO,MAAM,WAAA,CAAY,CAAA;AAC1D,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,aAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,mBAAQ,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,CAAE,QAAA,UAAY,UAAA,CAAW,UAAQ,CAAA;AAAA,IAC7F,SAAA,EAAW,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,GAAG,CAAA,CAAE,MAAA,mBAAQ,GAAA,CAAI,IAAA,CAAK,GAAG,CAAA,CAAE,QAAA,UAAY,UAAA,CAAW,UAAQ,CAAA;AAAA,IACtF,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,GAAI;AAAA,EACxC,CAAA;AACF;AAGO,IAAM,eAAA,EAAiB,EAAE,OAAA,EAAS,GAAA,EAAQ,QAAA,EAAU,IAAO,CAAA;AAGlE,SAAS,UAAA,CAAc,CAAA,EAAe,MAAA,EAA6C;AACjF,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,CAAA;AACpB,EAAA,GAAA,CAAI,MAAA,CAAO,OAAA,EAAS,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAI,YAAA,CAAa,uBAAA,EAAyB,YAAY,CAAC,CAAA;AACjG,EAAA,OAAO,IAAI,OAAA,CAAW,CAAC,OAAA,EAAS,MAAA,EAAA,GAAW;AACzC,IAAA,MAAM,QAAA,EAAU,CAAA,EAAA,GAAM,MAAA,CAAO,IAAI,YAAA,CAAa,uBAAA,EAAyB,YAAY,CAAC,CAAA;AACpF,IAAA,MAAA,CAAO,gBAAA,CAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,KAAK,CAAC,CAAA;AACxD,IAAA,CAAA,CAAE,IAAA;AAAA,MACA,CAAC,CAAA,EAAA,GAAM;AACL,QAAA,MAAA,CAAO,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAA;AAC3C,QAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,MACX,CAAA;AAAA,MACA,CAAC,CAAA,EAAA,GAAe;AACd,QAAA,MAAA,CAAO,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAA;AAC3C,QAAA,MAAA,CAAO,EAAA,WAAa,MAAA,EAAQ,EAAA,EAAI,IAAI,KAAA,CAAM,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,MACtD;AAAA,IACF,CAAA;AAAA,EACF,CAAC,CAAA;AACH;AAMA,MAAA,SAAe,UAAA,CAAW,CAAA,EAAiB,GAAA,EAAa,IAAA,EAAmB,EAAA,EAAqE;AAC9I,EAAA,MAAM,WAAA,EAAa,IAAI,eAAA,CAAgB,CAAA;AACvC,EAAA,IAAI,CAAA;AACJ,EAAA,MAAM,QAAA,EAAU,IAAI,OAAA,CAAe,CAAC,CAAA,EAAG,MAAA,EAAA,GAAW;AAChD,IAAA,EAAA,EAAI,UAAA,CAAW,CAAA,EAAA,GAAM;AACnB,MAAA,UAAA,CAAW,KAAA,CAAM,CAAA;AACjB,MAAA,MAAA,CAAO,IAAI,YAAA,CAAa,yBAAA,EAA2B,cAAc,CAAC,CAAA;AAAA,IACpE,CAAA,EAAG,EAAE,CAAA;AAAA,EACP,CAAC,CAAA;AACD,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,MAAA,CACvB,MAAA,CAAA,EAAA,GAAY;AACX,QAAA,MAAM,IAAA,EAAM,MAAM,CAAA,CAAE,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,OAAO,CAAC,CAAA;AAC/D,QAAA,MAAM,KAAA,EAAgB,MAAM,GAAA,CAAI,IAAA,CAAK,CAAA,CAAE,KAAA,CAAM,CAAA,EAAA,GAAM,IAAI,CAAA;AACvD,QAAA,OAAO,EAAE,EAAA,EAAI,GAAA,CAAI,EAAA,EAAI,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAAA,MAChD,CAAA,CAAA,CAAG,CAAA;AAAA,MACH;AAAA,IACF,CAAC,CAAA;AAAA,EACH,EAAA,QAAE;AACA,IAAA,YAAA,CAAa,CAAC,CAAA;AAAA,EAChB;AACF;AAEA,MAAA,SAAe,YAAA,CAAa,CAAA,EAAmC;AAC7D,EAAA,MAAM,EAAA,mBAAI,CAAA,CAAE,KAAA,UAAS,UAAA,CAAW,OAAA;AAChC,EAAA,MAAM,KAAA,EAAO;AAAA,IACX,IAAA,EAAM,CAAA,CAAE,KAAA;AAAA,IACR,SAAA,EAAW,CAAA,CAAE,SAAA;AAAA,IACb,QAAA,EAAU,CAAA,CAAE,KAAA;AAAA;AAAA,IACZ,aAAA,EAAe,CAAA,CAAE,aAAA;AAAA,IACjB,kBAAA,EAAoB,CAAA,CAAE,kBAAA;AAAA,IACtB,cAAA,EAAgB,iBAAA;AAAA,IAChB,mBAAA,EAAqB,iBAAA;AAAA,IACrB,SAAA,EAAW,cAAA;AAAA,IACX,MAAA,EAAQ,CAAA,CAAE,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,CAAA;AAAA,IAC5B,GAAA,EAAK,CAAC,EAAE,EAAA,EAAI,CAAA,CAAE,EAAA,CAAG,EAAA,EAAI,KAAA,EAAO,CAAA,CAAE,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,CAAA,EAAG,IAAA,EAAM,CAAA,CAAE,EAAA,CAAG,KAAK,CAAC;AAAA,EACtE,CAAA;AACA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,EAAM,MAAM,UAAA,CAAW,CAAA,EAAG,CAAA,EAAA;AAChB,MAAA;AACG,MAAA;AACc,MAAA;AACF,IAAA;AACf,EAAA;AACU,IAAA;AACtB,EAAA;AACiB,EAAA;AACE,EAAA;AACD,EAAA;AACE,EAAA;AACH,IAAA;AACK,IAAA;AACtB,EAAA;AAC4B,EAAA;AACb,IAAA;AACO,IAAA;AACtB,EAAA;AACwB,EAAA;AACJ,EAAA;AACtB;AAG4D;AACtC,EAAA;AACC,EAAA;AACE,EAAA;AACM,EAAA;AACD,EAAA;AACJ,EAAA;AACN,EAAA;AACE,IAAA;AACnB,EAAA;AAE0B,EAAA;AAC7B;AAS0D;AAGiF;AACpH,EAAA;AACQ,EAAA;AACA,EAAA;AACP,EAAA;AACf,EAAA;AACY,IAAA;AACS,IAAA;AACD,IAAA;AACP,IAAA;AACpB,EAAA;AACF;AAE2D;AACJ;AFxCxB;AACA;AG7MH;AACA;AACI;AACA;AAgBE;AAET,EAAA;AACI,kBAAA;AACT,IAAA;AACU,IAAA;AACT,EAAA;AAClB;AAOmB;AACK,EAAA;AACC,EAAA;AACR,EAAA;AACL,EAAA;AACJ,EAAA;AACU,oBAAA;AACb,IAAA;AACmB,MAAA;AACC,MAAA;AACJ,MAAA;AACP,QAAA;AACI,QAAA;AACf,MAAA;AACU,IAAA;AACoC,MAAA;AAEhD,IAAA;AACiB,oBAAA;AACO,IAAA;AACI,IAAA;AACN,IAAA;AACxB,EAAA;AACF;AHwL+B;AACA;AIvOhB;AAEC;AAGe;AACP,EAAA;AACE,EAAA;AAC1B;AAEmE;AACvB;AACX;AAGkB;AACzB,EAAA;AACT,EAAA;AACR,EAAA;AACT;AAEqD;AJkOtB;AACA;AKrQ/B;AACE;AACA;AACA;AACA;AACA;AACA;AACAA;AACAC;AACA;AACA;AACA;AAKK;AAMsB;AAIF;AACzB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGyB;AAEN;AAEM;AACH;AACF;AAI+B;AAC9B,EAAA;AACtB;AAEmD;AAEjB;AACa,EAAA;AAC/C;AAEsC;AACT,EAAA;AACH,IAAA;AACqB,MAAA;AACnB,MAAA;AACxB,IAAA;AAC2B,EAAA;AACP,IAAA;AACE,EAAA;AACK,IAAA;AACL,EAAA;AACZ,IAAA;AACZ,EAAA;AACF;AAMmB;AACA;AACF;AACW;AACoB;AAEsB;AACxC,EAAA;AACb,EAAA;AACY,EAAA;AACF,EAAA;AAC3B;AAEuC;AAC4E;AAGnC;AACpC,EAAA;AACd,EAAA;AACnB,EAAA;AACgB,IAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACI,MAAA;AACN,MAAA;AACpB,IAAA;AACO,IAAA;AACT,EAAA;AACwB,EAAA;AACK,IAAA;AACJ,IAAA;AACA,IAAA;AACG,IAAA;AACA,MAAA;AACN,MAAA;AACpB,IAAA;AACO,IAAA;AACT,EAAA;AAC6B,EAAA;AACH,EAAA;AACG,IAAA;AACP,IAAA;AACtB,EAAA;AACuB,EAAA;AACE,EAAA;AACA,EAAA;AACd,EAAA;AACe,IAAA;AACC,IAAA;AAC3B,EAAA;AAC2B,EAAA;AACf,EAAA;AACN,IAAA;AACuB,IAAA;AACA,IAAA;AACL,IAAA;AACA,IAAA;AACC,IAAA;AACG,IAAA;AAC5B,EAAA;AAC4B,EAAA;AAC9B;AAEmD;AAGF;AAK/C;AAKoB,EAAA;AACG,EAAA;AACK,EAAA;AACC,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACC,EAAA;AACI,EAAA;AACD,IAAA;AACH,IAAA;AACxB,EAAA;AAC0B,EAAA;AACE,IAAA;AACD,IAAA;AACP,IAAA;AACnB,EAAA;AAC4B,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACN,IAAA;AACxB,EAAA;AACsB,EAAA;AACX,IAAA;AAGX,EAAA;AAC2B,EAAA;AACD,IAAA;AACC,IAAA;AACE,IAAA;AACZ,MAAA;AACf,IAAA;AACa,IAAA;AACf,EAAA;AACyB,EAAA;AACV,IAAA;AACf,EAAA;AACoB,EAAA;AACtB;AAKwC;AACtB,EAAA;AACW,EAAA;AACP,EAAA;AACD,EAAA;AACO,EAAA;AACE,EAAA;AACxB,EAAA;AACsB,EAAA;AACE,IAAA;AAClB,IAAA;AACmB,EAAA;AACL,IAAA;AACK,MAAA;AAC3B,IAAA;AACQ,IAAA;AACmB,EAAA;AACA,IAAA;AACD,IAAA;AACrB,EAAA;AACa,IAAA;AACpB,EAAA;AAC6B,EAAA;AACF,EAAA;AACC,IAAA;AACI,IAAA;AACR,IAAA;AACxB,EAAA;AAC4B,EAAA;AAC9B;AAIE;AAKmB,EAAA;AACT,EAAA;AACa,EAAA;AACC,EAAA;AACK,EAAA;AAChB,EAAA;AACW,IAAA;AACQ,IAAA;AACN,IAAA;AAC2B,IAAA;AACjC,EAAA;AACY,IAAA;AACL,IAAA;AACP,EAAA;AACQ,IAAA;AACF,IAAA;AACG,IAAA;AACtB,EAAA;AACK,IAAA;AACZ,EAAA;AACF;AAE+B;AACR,EAAA;AACmB,EAAA;AACpB,EAAA;AACtB;AAQgB;AACJ,EAAA;AACa,EAAA;AACI,EAAA;AACC,EAAA;AACE,EAAA;AACN,EAAA;AACG,IAAA;AAC3B,EAAA;AAC2B,EAAA;AACb,EAAA;AAEc,EAAA;AAEG,EAAA;AACH,EAAA;AACoB,EAAA;AACtB,EAAA;AACF,IAAA;AACxB,EAAA;AACwB,EAAA;AACT,IAAA;AACf,EAAA;AAC2C,EAAA;AAClB,EAAA;AAGH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACxB;AAEoC;AACX,EAAA;AACzB;AAM6D;AAC9B,EAAA;AACX,EAAA;AACa,EAAA;AACX,EAAA;AACtB;AAMoE;AACtC,EAAA;AACC,EAAA;AACH,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACJ,EAAA;AAChB,EAAA;AACT;AAE6F;AAIrC;AAId;AACpB,EAAA;AACtB;AAEoC;AAC4C;AAG7B;AACxB,EAAA;AAC3B;AAGyE;AACpD,EAAA;AACO,EAAA;AACC,EAAA;AACK,EAAA;AACR,IAAA;AACE,IAAA;AACF,IAAA;AACvB,EAAA;AACH;AAEqC;AACb,EAAA;AACF,EAAA;AACtB;AAGuC;AACf,EAAA;AACL,EAAA;AACK,IAAA;AACtB,EAAA;AACiB,EAAA;AACnB;AAIE;AAGI,EAAA;AACyB,IAAA;AACjB,EAAA;AACe,IAAA;AAC3B,EAAA;AACF;AAEqD;AAC5B,EAAA;AACI,EAAA;AACL,EAAA;AACf,EAAA;AACT;AAE8C;AACf,EAAA;AACmC,IAAA;AAC1C,IAAA;AACI,IAAA;AACG,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACH,IAAA;AAC1B,EAAA;AAC0C,EAAA;AACrB,EAAA;AACD,EAAA;AACtB;AAGqD;AAC3B,EAAA;AACuD,EAAA;AACvD,EAAA;AACiC,EAAA;AAClD,EAAA;AACT;AAE+D;AAGI;AACrC,EAAA;AACN,EAAA;AACP,EAAA;AACjB;AAM+E;AACjD,EAAA;AACN,EAAA;AACxB;AAGE;AAE0B;AACE,EAAA;AACA,EAAA;AACd,EAAA;AACa,EAAA;AACA,IAAA;AACA,IAAA;AAC1B,EAAA;AACqB,EAAA;AACA,IAAA;AACtB,EAAA;AAC0B,EAAA;AAC5B;AAEqC;AACP,EAAA;AACA,EAAA;AACN,IAAA;AACtB,EAAA;AACyB,EAAA;AAC3B;AAE4B;AACL,EAAA;AACI,EAAA;AAC2B,EAAA;AAC/B,EAAA;AACO,EAAA;AAC9B;AAEgD;AAC9C,EAAA;AACA,EAAA;AACA,EAAA;AACuB,EAAA;AACzB;AAI4C;AACJ,EAAA;AACE,IAAA;AACrB,IAAA;AACK,IAAA;AACkB,MAAA;AACb,MAAA;AAC3B,IAAA;AACmC,IAAA;AACrC,EAAA;AACO,EAAA;AACT;AAEkC;AAC5B,EAAA;AACQ,IAAA;AACe,IAAA;AACL,IAAA;AACoE,IAAA;AAC7D,IAAA;AACF,IAAA;AACnB,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAEuC;AACV,EAAA;AACA,EAAA;AACV,EAAA;AACM,IAAA;AACvB,EAAA;AACyB,EAAA;AACJ,EAAA;AACJ,EAAA;AACQ,EAAA;AACJ,EAAA;AACvB;AAI4B;AACtB,EAAA;AACsB,IAAA;AACd,EAAA;AACiB,IAAA;AAC7B,EAAA;AACF;AAE4C;AAClB,EAAA;AACpB,EAAA;AACwB,IAAA;AAChB,EAAA;AACgB,IAAA;AACN,IAAA;AACtB,EAAA;AACF;AAG2C;AACvB,EAAA;AACM,EAAA;AACG,EAAA;AACA,IAAA;AAClB,IAAA;AACT,EAAA;AACO,EAAA;AACT;AAI6C;AACd,EAAA;AACX,EAAA;AACC,EAAA;AACC,EAAA;AACO,EAAA;AACC,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACvB,EAAA;AACsB,EAAA;AACM,EAAA;AACtB,EAAA;AACW,IAAA;AACQ,IAAA;AACE,IAAA;AACb,IAAA;AACY,IAAA;AACJ,IAAA;AACG,IAAA;AACX,IAAA;AACM,IAAA;AACI,IAAA;AACzB,EAAA;AACF;AAIE;AAG6B,EAAA;AACX,EAAA;AACS,EAAA;AACJ,EAAA;AACK,EAAA;AACA,EAAA;AAEC,EAAA;AACL,IAAA;AACA,IAAA;AACG,IAAA;AAErB,IAAA;AACc,MAAA;AAEM,IAAA;AACzB,EAAA;AAE4B,EAAA;AACX,EAAA;AACE,EAAA;AAEuB,EAAA;AACjC,EAAA;AACY,EAAA;AACG,IAAA;AACT,EAAA;AAGsBA,IAAAA;AAChC,IAAA;AACsB,MAAA;AACJ,MAAA;AACZ,QAAA;AACP,MAAA;AACS,IAAA;AACe,MAAA;AACF,MAAA;AACzB,IAAA;AACF,EAAA;AACuB,EAAA;AACG,EAAA;AAEnB,EAAA;AACL,IAAA;AACA,IAAA;AACoB,IAAA;AACpB,IAAA;AACA,IAAA;AAC0B,IAAA;AACR,IAAA;AACpB,EAAA;AACF;AAE8C;AACvC,EAAA;AACkB,EAAA;AACM,EAAA;AACX,EAAA;AACS,EAAA;AACH,EAAA;AACI,EAAA;AACN,EAAA;AACM,EAAA;AACf,EAAA;AACX,IAAA;AAC2B,IAAA;AACF,IAAA;AAC3B,EAAA;AACmC,EAAA;AACxB,EAAA;AACS,IAAA;AACM,MAAA;AACtB,MAAA;AACF,IAAA;AACgB,IAAA;AACJ,MAAA;AACR,QAAA;AACS,QAAA;AACD,QAAA;AACe,QAAA;AACzB,MAAA;AACF,IAAA;AACF,EAAA;AACwB,EAAA;AAC1B;AAE6C;AACzB,EAAA;AACM,EAAA;AAC1B;AAE8D;ALqH/B;AACA;AC/sBZ;AACH;AAC2C;AAEA;AAC5B,EAAA;AAC/B;AAEwC;AAGS;AACzB,EAAA;AACxB;AAEqD;AAC3B,EAAA;AAC1B;AAGuB;AAGyB;AAC1B,EAAA;AACK,EAAA;AACD,EAAA;AACK,EAAA;AAC/B;AAGsB;AACY,EAAA;AACzB,EAAA;AACT;AAE0B;AAEoB;AACxC,EAAA;AACuB,IAAA;AACnB,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAEkD;AAIzB;AACA,EAAA;AACzB;AAGwB;AACC,EAAA;AACzB;AAEuB;AAEsD;AAC/C,EAAA;AACoB,EAAA;AAClB,EAAA;AAChC;AAEwB;AAClB,EAAA;AACmB,IAAA;AACD,IAAA;AACd,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAcgD;AAIY;AAC/B,EAAA;AAEA,EAAA;AAER,EAAA;AACC,EAAA;AAEF,EAAA;AAIU,EAAA;AACJ,EAAA;AACA,EAAA;AACoB,EAAA;AACrB,EAAA;AACmB,EAAA;AACb,EAAA;AACe,EAAA;AACf,EAAA;AACH,EAAA;AACC,EAAA;AACkD,EAAA;AAC1B,EAAA;AAC5B,EAAA;AAEA,EAAA;AAEA,EAAA;AAIP,EAAA;AACK,IAAA;AACA,IAAA;AACK,IAAA;AACzB,EAAA;AAEgD,EAAA;AAEzB,IAAA;AACK,IAAA;AACV,IAAA;AACd,EAAA;AAEqB,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACG,IAAA;AACK,IAAA;AACzB,MAAA;AACS,MAAA;AACC,MAAA;AACG,MAAA;AACD,MAAA;AACQ,MAAA;AACK,MAAA;AACzB,MAAA;AACG,MAAA;AACL,IAAA;AAEI,IAAA;AAAiB,MAAA;AAAW,IAAA;AAA0C,IAAA;AACvE,EAAA;AAIa,EAAA;AACQ,IAAA;AACC,IAAA;AACT,MAAA;AACA,MAAA;AAChB,IAAA;AACY,IAAA;AACT,EAAA;AAEU,EAAA;AACyC,IAAA;AAC5B,MAAA;AACP,MAAA;AAAA;AAEO,MAAA;AAC1B,IAAA;AACU,IAAA;AACZ,EAAA;AAEiB,EAAA;AACwD,IAAA;AACrD,MAAA;AACZ,MAAA;AACiB,QAAA;AACC,QAAA;AACH,QAAA;AACP,MAAA;AACU,QAAA;AACJ,QAAA;AACE,QAAA;AACI,QAAA;AACxB,MAAA;AACF,IAAA;AACiB,IAAA;AACnB,EAAA;AAEsB,EAAA;AACc,IAAA;AACX,MAAA;AACH,MAAA;AACK,sBAAA;AACF,MAAA;AACS,MAAA;AACZ,MAAA;AACO,MAAA;AACL,MAAA;AACtB,IAAA;AACmB,IAAA;AACrB,EAAA;AAEmB,EAAA;AACmB,IAAA;AACb,MAAA;AACC,MAAA;AACJ,MAAA;AACE,QAAA;AAClB,QAAA;AACF,MAAA;AACqB,MAAA;AACV,MAAA;AACc,MAAA;AACJ,MAAA;AACvB,IAAA;AACmB,IAAA;AACrB,EAAA;AAE4B,EAAA;AAGb,EAAA;AACO,IAAA;AACnB,IAAA;AACH,EAAA;AAIuB,EAAA;AACI,IAAA;AACH,MAAA;AACJ,MAAA;AACS,MAAA;AACA,MAAA;AAGJ,MAAA;AACN,QAAA;AACD,UAAA;AACZ,QAAA;AACF,MAAA;AACoB,MAAA;AACL,QAAA;AACA,QAAA;AACK,QAAA;AACI,UAAA;AACtB,QAAA;AAGF,MAAA;AAEkC,MAAA;AACI,MAAA;AACvBC,QAAAA;AACNA,QAAAA;AACT,MAAA;AAEsB,MAAA;AACR,QAAA;AACQ,QAAA;AAGX,QAAA;AACX,MAAA;AAEoB,MAAA;AACF,QAAA;AACD,QAAA;AACbC,UAAAA;AACe,UAAA;AACN,UAAA;AACM,UAAA;AAEE,YAAA;AACjB,UAAA;AACF,QAAA;AACIC,QAAAA;AACA,QAAA;AACc,UAAA;AACN,QAAA;AACG,UAAA;AACQ,UAAA;AAED,UAAA;AACP,UAAA;AACG,UAAA;AAClB,QAAA;AACa,QAAA;AACQ,QAAA;AACdA,QAAAA;AACT,MAAA;AAGY,MAAA;AACY,MAAA;AACL,QAAA;AACG,QAAA;AACtB,MAAA;AACY,MAAA;AAGuB,MAAA;AACvB,MAAA;AAGM,MAAA;AACD,QAAA;AACX,QAAA;AACgB,UAAA;AACJ,UAAA;AACF,YAAA;AACZ,UAAA;AACU,QAAA;AACU,UAAA;AACH,UAAA;AACb,UAAA;AACQ,YAAA;AACZ,UAAA;AACM,UAAA;AACR,QAAA;AACY,QAAA;AACK,QAAA;AACG,QAAA;AACP,QAAA;AACR,QAAA;AACkB,QAAA;AACV,QAAA;AACE,QAAA;AACO,UAAA;AACtB,QAAA;AAEkB,QAAA;AACL,QAAA;AACO,QAAA;AACH,UAAA;AACI,UAAA;AACD,UAAA;AACZ,UAAA;AACP,QAAA;AACW,QAAA;AACU,QAAA;AACJ,QAAA;AACA,QAAA;AACG,QAAA;AACC,UAAA;AACtB,QAAA;AACqB,QAAA;AACT,QAAA;AACd,MAAA;AAGuB,MAAA;AACX,MAAA;AAGK,MAAA;AACE,MAAA;AACN,MAAA;AACI,MAAA;AACM,MAAA;AAED,MAAA;AACT,MAAA;AACS,MAAA;AACC,MAAA;AACD,QAAA;AACtB,MAAA;AACe,MAAA;AACO,QAAA;AACD,QAAA;AACpB,MAAA;AACoC,MAAA;AACpB,MAAA;AACQ,MAAA;AAC3B,IAAA;AACoB,IAAA;AACtB,EAAA;AAKoB,EAAA;AACyB,IAAA;AACrB,MAAA;AACK,MAAA;AACJ,MAAA;AACN,QAAA;AACD,UAAA;AACZ,QAAA;AACF,MAAA;AACsB,MAAA;AACT,MAAA;AACW,MAAA;AACH,MAAA;AAEC,QAAA;AACtB,MAAA;AACY,MAAA;AACO,MAAA;AACG,MAAA;AACA,MAAA;AACA,QAAA;AACtB,MAAA;AACsB,MAAA;AACD,QAAA;AACF,QAAA;AACG,QAAA;AACtB,MAAA;AAEwB,MAAA;AACY,MAAA;AACtB,MAAA;AACR,QAAA;AACe,UAAA;AACH,UAAA;AACH,YAAA;AACX,UAAA;AACM,QAAA;AAER,QAAA;AACa,QAAA;AACf,MAAA;AACe,MAAA;AACY,MAAA;AACzB,QAAA;AACa,QAAA;AACb,QAAA;AACoB,QAAA;AACtB,MAAA;AAEoB,MAAA;AACH,MAAA;AACQ,MAAA;AAC3B,IAAA;AAC0B,IAAA;AAC5B,EAAA;AAE4B,EAAA;AACD,IAAA;AACH,MAAA;AACJ,MAAA;AACS,MAAA;AACT,MAAA;AACO,MAAA;AACH,MAAA;AACL,QAAA;AACD,UAAA;AACZ,QAAA;AACa,QAAA;AACK,QAAA;AACI,UAAA;AACtB,QAAA;AACF,MAAA;AAGY,MAAA;AACY,MAAA;AACL,QAAA;AACG,QAAA;AACtB,MAAA;AACY,MAAA;AAGO,MAAA;AACK,MAAA;AACZ,MAAA;AACoB,MAAA;AAC9B,QAAA;AACe,QAAA;AACI,QAAA;AACJ,QAAA;AACK,QAAA;AACX,QAAA;AACF,QAAA;AACM,QAAA;AACd,MAAA;AACW,MAAA;AACU,MAAA;AACF,MAAA;AACA,QAAA;AACE,QAAA;AACtB,MAAA;AACY,MAAA;AAIa,MAAA;AACb,MAAA;AACG,MAAA;AACH,MAAA;AAEY,MAAA;AACF,QAAA;AACtB,MAAA;AACI,MAAA;AACA,MAAA;AACe,QAAA;AACJF,QAAAA;AACQ,QAAA;AACF,UAAA;AACR,UAAA;AACE,UAAA;AACC,UAAA;AACK,UAAA;AACD,UAAA;AACjB,QAAA;AAEkB,QAAA;AACI,QAAA;AACvB,MAAA;AACkB,QAAA;AACpB,MAAA;AACyB,MAAA;AACF,MAAA;AACN,MAAA;AACM,MAAA;AAGD,MAAA;AACN,MAAA;AACM,QAAA;AACA,QAAA;AACtB,MAAA;AACkB,MAAA;AACpB,IAAA;AACyB,IAAA;AAC3B,EAAA;AAG0B,EAAA;AACmC,IAAA;AACzB,MAAA;AACX,MAAA;AACJ,MAAA;AACA,MAAA;AACQ,MAAA;AACrB,MAAA;AACgB,QAAA;AACR,MAAA;AACG,QAAA;AACG,QAAA;AACC,QAAA;AACD,QAAA;AAChB,MAAA;AACqB,QAAA;AACvB,MAAA;AACF,IAAA;AACiB,IAAA;AACnB,EAAA;AAE4B,EAAA;AACJ,IAAA;AACK,IAAA;AACA,IAAA;AACP,MAAA;AACM,MAAA;AACN,MAAA;AACE,MAAA;AACI,MAAA;AACxB,MAAA;AACF,IAAA;AAEmB,IAAA;AACK,IAAA;AAGT,IAAA;AACK,MAAA;AACM,MAAA;AACP,MAAA;AACG,MAAA;AACC,MAAA;AACjB,MAAA;AACkB,QAAA;AACL,QAAA;AACO,QAAA;AACP,QAAA;AACD,QAAA;AACE,QAAA;AACA,UAAA;AACC,0BAAA;AAED,QAAA;AACV,MAAA;AACiB,QAAA;AACvB,MAAA;AACc,QAAA;AAChB,MAAA;AACA,MAAA;AACF,IAAA;AAIyB,IAAA;AACL,IAAA;AACD,IAAA;AACO,MAAA;AACxB,MAAA;AACF,IAAA;AAGe,IAAA;AACQ,MAAA;AACA,MAAA;AACA,MAAA;AACJ,MAAA;AAEQ,MAAA;AACVG,MAAAA;AACf,MAAA;AACF,IAAA;AAEuB,IAAA;AACL,IAAA;AACG,IAAA;AACA,IAAA;AACJ,IAAA;AAEI,IAAA;AACA,IAAA;AACW,IAAA;AACjB,MAAA;AACW,MAAA;AACT,MAAA;AACK,MAAA;AACR,MAAA;AACC,MAAA;AACE,MAAA;AACA,MAAA;AAChB,IAAA;AACoB,IAAA;AACJ,IAAA;AACQ,IAAA;AAErB,IAAA;AACoB,MAAA;AACX,MAAA;AACD,IAAA;AACc,MAAA;AACA,MAAA;AACP,MAAA;AACD,MAAA;AAChB,IAAA;AACqB,MAAA;AACvB,IAAA;AACkBA,EAAAA;AAEK,EAAA;AACV,IAAA;AACH,EAAA;AAKI,EAAA;AACI,IAAA;AACL,IAAA;AACO,MAAA;AACD,sBAAA;AACC,MAAA;AACpB,IAAA;AACG,EAAA;AAGW,EAAA;AAC4B,IAAA;AACnB,MAAA;AACT,MAAA;AACY,MAAA;AACT,MAAA;AACE,MAAA;AACnB,IAAA;AACyB,IAAA;AACR,IAAA;AACG,IAAA;AACP,IAAA;AACC,MAAA;AACd,IAAA;AACW,EAAA;AAGe,EAAA;AACZ,EAAA;AACE,IAAA;AACO,IAAA;AACC,IAAA;AACC,oBAAA;AACJ,EAAA;AAGI,EAAA;AAEE,EAAA;AACb,EAAA;AACF,IAAA;AACI,IAAA;AACQ,IAAA;AAChB,IAAA;AACA,MAAA;AACO,QAAA;AACb,MAAA;AACgB,MAAA;AACF,QAAA;AACI,QAAA;AACA,QAAA;AAClB,MAAA;AACF,IAAA;AACa,IAAA;AACH,MAAA;AACV,IAAA;AAC2B,EAAA;AAIb,EAAA;AACQ,IAAA;AACR,IAAA;AACS,IAAA;AACJ,MAAA;AACE,MAAA;AACF,MAAA;AACnB,IAAA;AACqB,IAAA;AACD,MAAA;AACO,MAAA;AAChB,IAAA;AACS,MAAA;AACF,MAAA;AAClB,IAAA;AACD,EAAA;AAKyB,EAAA;AACV,EAAA;AACI,IAAA;AACS,IAAA;AACN,IAAA;AACF,IAAA;AACQ,IAAA;AACJ,IAAA;AACF,EAAA;AAGI,EAAA;AACE,EAAA;AACb,EAAA;AACW,IAAA;AACJ,IAAA;AACA,IAAA;AACM,EAAA;AAGJ,EAAA;AACT,EAAA;AACO,IAAA;AACM,IAAA;AACN,IAAA;AACD,EAAA;AAGK,EAAA;AACD,EAAA;AACF,EAAA;AACD,EAAA;AACP,EAAA;AACG,IAAA;AAEA,IAAA;AACI,IAAA;AACM,IAAA;AACN,IAAA;AACC,IAAA;AACK,IAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACf,IAAA;AACM,IAAA;AACZ,MAAA;AACiB,QAAA;AACF,UAAA;AACK,UAAA;AACD,UAAA;AACnB,UAAA;AACD,QAAA;AACkB,QAAA;AAEA,QAAA;AAGG,QAAA;AAGJ,QAAA;AACC,QAAA;AACA,QAAA;AACE,QAAA;AACJ,QAAA;AACA,QAAA;AACG,UAAA;AAClB,UAAA;AACF,QAAA;AAEsB,QAAA;AACC,QAAA;AACV,UAAA;AACH,UAAA;AACO,UAAA;AACJ,UAAA;AACQ,UAAA;AACN,UAAA;AACE,UAAA;AACA,UAAA;AACjB,QAAA;AACgB,QAAA;AACZ,QAAA;AACA,QAAA;AACkB,UAAA;AACd,QAAA;AACM,UAAA;AACM,UAAA;AACA,UAAA;AAClB,UAAA;AACF,QAAA;AACmB,QAAA;AACA,QAAA;AACI,UAAA;AAIP,UAAA;AACf,QAAA;AACkB,QAAA;AAGA,QAAA;AACA,QAAA;AACQ,QAAA;AACE,QAAA;AACzB,QAAA;AACkB,UAAA;AACG,UAAA;AACJ,UAAA;AACT,YAAA;AACH,UAAA;AACW,YAAA;AACD,YAAA;AACF,YAAA;AACf,UAAA;AACM,QAAA;AAER,QAAA;AACmB,QAAA;AACF,QAAA;AACP,MAAA;AACS,QAAA;AACF,QAAA;AACnB,MAAA;AACC,IAAA;AACU,IAAA;AACH,MAAA;AACV,IAAA;AACuB,EAAA;AAGA,EAAA;AACT,EAAA;AACA,IAAA;AACM,IAAA;AACO,IAAA;AACN,IAAA;AACE,IAAA;AAEG,IAAA;AACJ,IAAA;AACE,IAAA;AACF,IAAA;AACN,IAAA;AACQ,EAAA;AAGV,EAAA;AACY,IAAA;AACA,MAAA;AACC,MAAA;AACd,IAAA;AACiB,IAAA;AACf,MAAA;AACf,IAAA;AAC0B,IAAA;AACb,IAAA;AACQ,MAAA;AACV,MAAA;AACX,IAAA;AACa,EAAA;AAGU,EAAA;AAGT,EAAA;AACC,IAAA;AACW,IAAA;AACL,MAAA;AACA,MAAA;AACD,MAAA;AACO,MAAA;AACP,MAAA;AACV,IAAA;AACiB,IAAA;AACL,EAAA;AAG0B,EAAA;AAClC,EAAA;AACa,IAAA;AACF,IAAA;AACN,IAAA;AACI,IAAA;AACC,EAAA;AAIN,EAAA;AACA,IAAA;AACG,MAAA;AACP,MAAA;AACa,MAAA;AACA,MAAA;AACZ,MAAA;AACI,QAAA;AACf,QAAA;AACF,MAAA;AACwB,MAAA;AACH,QAAA;AACnB,QAAA;AACF,MAAA;AACiB,MAAA;AACE,MAAA;AACA,QAAA;AACnB,MAAA;AACF,IAAA;AACS,IAAA;AACX,EAAA;AAGqE,EAAA;AAK3D,EAAA;AAEuB,IAAA;AACA,IAAA;AACD,IAAA;AACF,IAAA;AACH,IAAA;AACC,IAAA;AAEtB,EAAA;AAEC,EAAA;AACY,IAAA;AACC,IAAA;AACC,IAAA;AACL,IAAA;AACd,IAAA;AACkB,IAAA;AAClB,IAAA;AACa,IAAA;AACa,IAAA;AAC1B,IAAA;AACgB,IAAA;AAChB,IAAA;AAAA;AAEwB,IAAA;AAAwD;AAEvD,IAAA;AACL,IAAA;AACtB,EAAA;AACF;AD0hB+B;AACA;AM3kDP;AAKfL;AAKwB;AAEC;AACZ;AAgCQ;AACA,EAAA;AACA,EAAA;AAC9B;AAG+C;AAC3B,EAAA;AACW,EAAA;AACDM,EAAAA;AACF,EAAA;AACFA,EAAAA;AAER,EAAA;AACQ,IAAA;AACK,IAAA;AACxB,EAAA;AAEsB,EAAA;AAET,EAAA;AACK,EAAA;AACL,IAAA;AACJ,IAAA;AACd,EAAA;AACgB,EAAA;AACH,IAAA;AACY,IAAA;AACE,IAAA;AACD,MAAA;AACC,QAAA;AACD,QAAA;AACX,UAAA;AACW,YAAA;AAClB,UAAA;AACgB,UAAA;AAEC,YAAA;AACJ,YAAA;AACb,UAAA;AACF,QAAA;AACF,MAAA;AACmB,IAAA;AACR,IAAA;AACO,MAAA;AACD,MAAA;AACnB,IAAA;AAEY,EAAA;AAEE,EAAA;AACW,IAAA;AACb,IAAA;AACa,IAAA;AACZ,MAAA;AACa,QAAA;AACxB,MAAA;AACM,MAAA;AACkB,QAAA;AACxB,MAAA;AACF,IAAA;AACa,IAAA;AACH,MAAA;AACV,IAAA;AAEwB,EAAA;AAEXC,EAAAA;AACQ,IAAA;AACI,MAAA;AACJ,QAAA;AACU,QAAA;AAC7B,MAAA;AACF,IAAA;AACS,IAAA;AACX,EAAA;AAEmB,EAAA;AAET,EAAA;AAEkB,IAAA;AACJ,IAAA;AACM,IAAA;AACnB,IAAA;AACL,MAAA;AACuB,MAAA;AACD,MAAA;AACtB,MAAA;AACwB,MAAA;AACA,MAAA;AACJ,MAAA;AACK,MAAA;AACJ,MAAA;AACvB,IAAA;AACD,EAAA;AACL;AAE+C;AAC7B,EAAA;AACI,EAAA;AACQ,EAAA;AAC9B;AAOoE;AACvC,EAAA;AACJ,EAAA;AACC,EAAA;AACjB,EAAA;AACL,IAAA;AACmB,IAAA;AACd,IAAA;AACH,MAAA;AACE,QAAA;AACA,QAAA;AACW,UAAA;AACH,UAAA;AACe,UAAA;AACA,UAAA;AACD,UAAA;AACA,UAAA;AAAA;AAEN,UAAA;AACD,UAAA;AACf,QAAA;AACkB,QAAA;AACpB,MAAA;AACF,IAAA;AACF,EAAA;AACF;ANihD+B;AACA;AOtsDtBC;AAe0C;AACzB,EAAA;AACP,EAAA;AACV,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACQ,IAAA;AACG,MAAA;AACO,MAAA;AAC3B,IAAA;AACK,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACI,IAAA;AACK,MAAA;AACI,MAAA;AACD,QAAA;AACnB,MAAA;AACgB,MAAA;AACG,MAAA;AACA,MAAA;AACG,MAAA;AACxB,IAAA;AACK,IAAA;AACI,MAAA;AACkB,IAAA;AACF,MAAA;AAEN,MAAA;AAEM,MAAA;AACzB,IAAA;AACK,IAAA;AACI,MAAA;AACJ,IAAA;AACiB,MAAA;AACjB,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACX,EAAA;AACF;AAE8D;AACjC,EAAA;AACF,EAAA;AAED,EAAA;AAEC,EAAA;AAClBA,EAAAA;AACL,IAAA;AACA,IAAA;AACQ,MAAA;AACN,MAAA;AACgB,MAAA;AACC,MAAA;AACO,MAAA;AACX,MAAA;AACF,MAAA;AACb,IAAA;AACY,qBAAA;AACd,EAAA;AACF;APqrD+B;AACA;AQ/wDR;ARixDQ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/index.cjs","sourcesContent":[null,"// Story s7: the React layer. One useReducer over core/machine per instance, no globals.\n// Spec: docs/specs/mint-button.html #package-api, #ui-ux-flow, #state-management, #edge-cases.\nimport { useCallback, useEffect, useReducer, useRef, useState } from 'react';\nimport { BaseError, ExecutionRevertedError, publicActions, type PublicClient, type WalletClient } from 'viem';\n\nimport { chainName, createReadClient, getChain } from '../core/rpc';\nimport { getAccount, getConnectedBalance, getWalletClient, loadConnectWidget, openConnectModal, subscribeAccount, switchToChain, waitForDeposit } from '../core/wallet';\nimport { getQuote, isRelayTestnet, payChainsFor, type RelayQuote } from '../core/relay/client';\nimport { pollUntilTerminal } from '../core/relay/status';\nimport { claimDeposit, clearOutstanding, getOutstanding, outstandingKey, releaseDeposit, setOutstanding, type Outstanding } from '../core/relay/outstanding';\nimport { manifoldClaimAdapter } from '../core/adapters/manifold-claim';\nimport { contractAdapter } from '../core/adapters/contract';\nimport { parseMintReceipt } from '../core/adapters/receipt';\nimport { MintError, UNSUPPORTED_REASONS, isMintError, toReason } from '../core/errors';\nimport type { ErrorReason, ReasonCode, UnsupportedReason } from '../core/errors';\nimport { friendlyErrorMessage } from '../core/friendly-errors';\nimport {\n IN_FLIGHT_STATES,\n canAct as coreCanAct,\n initialState,\n reducer,\n selectMaxQuantity,\n selectTotal,\n} from '../core/machine';\nimport type { Account, MachineState, MintStateName, PayShort } from '../core/machine';\nimport type { AdapterContext, MintAdapter, MintPlan, ReceiptLog, TxRequest, WalletRead } from '../core/adapters/types';\nimport type { Address, Attempt, CrossChainOption, DropInfo, Hex, MintConfig, MintEvent, MintEventType, MintResult, Money, Route, RpcUrls, WalletInfo } from '../core/types';\n\n/** Options for useMint and the props MintButton forwards to it (spec #package-api). */\nexport type UseMintOptions = {\n config: MintConfig;\n /** chainId -> JSON-RPC URL (agent's domain-restricted key). */\n rpcUrls?: RpcUrls;\n /** Defaults to the connected wallet. */\n recipient?: Address;\n /** Controlled when onQuantityChange is also supplied. */\n quantity?: number;\n onQuantityChange?: (quantity: number) => void;\n connect?: { walletConnectProjectId?: string };\n onEvent?: (event: MintEvent) => void;\n /**\n * Relay cross-chain mint for Manifold claims. Default on: a wallet on another supported chain pays\n * there and Relay mints on the drop chain. false turns it off; `chains` narrows the pay chains.\n */\n crossChain?: CrossChainOption;\n /** Force the pay chain. The drop chain means a direct mint. Must be one of the pay chains. */\n payChainId?: number;\n};\n\n/** What PayWithPicker needs to quote other chains (spec #public-api). */\nexport type CrossChainInfo = {\n dropChainId: number;\n /** Chains the collector may pay from, the drop chain included. */\n payChains: number[];\n /** The destination call (plan.mint for the current quantity and recipient). */\n tx: TxRequest;\n payer: Address;\n recipient: Address;\n testnet: boolean;\n};\n\n/** Spec #data-models table 2. */\nexport type MintState = {\n state: MintStateName;\n reason: ReasonCode | null;\n message: string | null;\n drop: DropInfo | null;\n wallet: WalletInfo | null;\n quantity: number;\n setQuantity: (quantity: number) => void;\n maxQuantity: number | null;\n total: { native: Money; erc20: Money | null } | null;\n mint: () => void;\n result: MintResult | null;\n refresh: () => Promise<void>;\n canAct: boolean;\n /** How this wallet pays: direct on the drop chain, or Relay from its current chain. null before a wallet read. */\n route: Route | null;\n /** Inputs for PayWithPicker; null when Relay cannot mint this drop for this wallet. */\n crossChain: CrossChainInfo | null;\n};\n\nconst REFRESH_MS = 30_000;\nconst TICK_MS = 1_000;\nconst ASYNC_STATES: readonly MintStateName[] = ['loading', 'connecting', 'switching-chain', 'approving', 'minting', 'confirming'];\n\nexport function isAsyncState(name: MintStateName): boolean {\n return ASYNC_STATES.includes(name);\n}\n\nconst nowSec = (): number => Math.floor(Date.now() / 1000);\n\n/** Stable identity for a config: a change starts a new read lifetime (new AbortController + cacheScope). */\nfunction configKeyOf(config: MintConfig): string {\n return JSON.stringify(config, (_k, v) => (typeof v === 'bigint' ? { __dropkitBigInt: v.toString() } : v));\n}\n\nfunction adapterFor(config: MintConfig): MintAdapter {\n return (config.type === 'manifold-claim' ? manifoldClaimAdapter : contractAdapter) as unknown as MintAdapter;\n}\n\n/** The Manifold read bridge only serves the Manifold preset's allowlisted contracts (spec #api-endpoints). */\nconst bridgeEnabled = (config: MintConfig): boolean => config.type === 'manifold-claim';\n\n/** Pay chains for this config and option (drop chain included); empty = no Relay (spec R1, R2). */\nexport function payChainsOf(config: MintConfig, option: CrossChainOption | undefined): number[] {\n if (config.type !== 'manifold-claim' || option === false) return [];\n const all = payChainsFor(config.chainId);\n const narrowed = typeof option === 'object' && option.chains ? all.filter((id) => id === config.chainId || option.chains!.includes(id)) : all;\n return narrowed.length > 1 ? narrowed : [];\n}\n\n/** The chain this wallet would pay on: payChainId when valid, else the wallet's chain (spec R3, R4). */\nfunction routeChainOf(payChains: readonly number[], accountChainId: number, payChainId: number | undefined): number {\n if (payChainId !== undefined && payChains.includes(payChainId)) return payChainId;\n return accountChainId;\n}\n\nconst ROUTE_DEBOUNCE_MS = 500;\n\nconst symbolOf = (chainId: number): string => {\n try {\n return getChain(chainId).nativeCurrency.symbol;\n } catch {\n return 'ETH';\n }\n};\n\nconst shortOf = (chainId: number): PayShort => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });\n\n\n/** Logs come from the creator contract for the preset, else config.tokenContract ?? the mint target. */\nfunction tokenContractOf(config: MintConfig): Address {\n return config.type === 'manifold-claim' ? config.creatorContract : (config.tokenContract ?? config.address);\n}\n\n/** onEvent payload address: the preset reports the creator contract (never a wallet). */\nfunction eventAddressOf(config: MintConfig): Address {\n return config.type === 'manifold-claim' ? config.creatorContract : config.address;\n}\n\nconst isUnsupported = (reason: ReasonCode): reason is UnsupportedReason => (UNSUPPORTED_REASONS as readonly string[]).includes(reason);\n\nfunction errorReasonOf(e: unknown): { reason: ErrorReason; message: string } {\n const { reason, message } = toReason(e);\n const ok: readonly string[] = ['USER_REJECTED', 'TX_REVERTED', 'RATE_LIMITED', 'RPC_ERROR', 'CHAIN_SWITCH_FAILED', 'ALLOWLIST_UNAVAILABLE'];\n return { reason: (ok.includes(reason) ? reason : 'RPC_ERROR') as ErrorReason, message };\n}\n\nfunction explorerUrlFor(chainId: number, hash: Hex): string | null {\n try {\n const url = getChain(chainId).blockExplorers?.default.url;\n return url ? `${url.replace(/\\/$/, '')}/tx/${hash}` : null;\n } catch {\n return null;\n }\n}\n\n/** A read lifetime: one AbortController + one stable cacheScope per hook/config, shared across refreshes. */\ntype Lifetime = {\n key: string;\n controller: AbortController;\n /** Stable object identity for the whole lifetime (adapters key their shared fetches on it). */\n cacheScope: object;\n /** Current read generation; a result from another generation is dropped. */\n gen: number;\n client?: PublicClient;\n rpcUrl?: string;\n};\n\nconst receiptOk = (status: unknown): boolean => status === 'success' || status === 1 || status === 1n || status === '0x1';\n\ntype Receiptish = { status?: unknown; logs?: readonly ReceiptLog[]; transactionHash?: Hex };\n\nexport function useMint(options: UseMintOptions): MintState {\n const { config, recipient, quantity: quantityProp, connect } = options;\n\n const [machine, rawDispatch] = useReducer(reducer, undefined, () => initialState({ quantity: quantityProp }));\n /** Widget verdict is held OUTSIDE the reducer so a later successful drop read cannot overwrite it (AC-20). */\n const [widgetFail, setWidgetFail] = useState<{ reason: ReasonCode; message: string } | null>(null);\n const [widgetRetry, setWidgetRetry] = useState(0);\n\n const configKey = configKeyOf(config);\n\n\n // --- refs: everything the stable callbacks and async flows read.\n const mounted = useRef(false);\n const stateRef = useRef(machine);\n const propsRef = useRef(options);\n const lifeRef = useRef<Lifetime | null>(null);\n const genRef = useRef(0);\n const attemptRef = useRef<Attempt | null>(null);\n const attemptIdRef = useRef(0);\n const accountKeyRef = useRef<string | null>(null);\n const connectingRef = useRef(false);\n const pendingRefreshRef = useRef(false);\n const pendingQuantityRef = useRef<number | null>(null);\n const widgetFailRef = useRef<{ reason: ReasonCode; message: string } | null>(null);\n const widgetLoadRef = useRef<Promise<void> | null>(null);\n const pushAccountRef = useRef<((a: Account | null) => void) | null>(null);\n /** Outstanding-request keys this mount already resumed or paid itself. */\n const autoResumedRef = useRef(new Set<string>());\n /** Picker inputs, tagged with the walletGen they were built for: never shown for another account/quantity. */\n const [crossChainInfo, setCrossChainInfo] = useState<{ gen: number; info: CrossChainInfo } | null>(null);\n\n // Mirrors of the committed render: the async flows and the stable callbacks read these instead of\n // closing over one render's values. Written on commit, never during render.\n useEffect(() => {\n stateRef.current = machine;\n propsRef.current = options;\n widgetFailRef.current = widgetFail;\n });\n\n const dispatch = useCallback<typeof rawDispatch>((event) => {\n // Nothing may touch React state after unmount (spec #edge-cases) or for a dead lifecycle.\n if (!mounted.current) return;\n stateRef.current = reducer(stateRef.current, event);\n rawDispatch(event);\n }, []);\n\n const emit = useCallback((type: MintEventType, over: Partial<MintEvent> = {}) => {\n const handler = propsRef.current.onEvent;\n if (!handler || !mounted.current) return;\n const cfg = attemptRef.current?.config ?? propsRef.current.config;\n const s = stateRef.current;\n const originChainId = attemptRef.current?.originChainId ?? (s.route?.kind === 'relay' ? s.route.originChainId : (s.short?.chainId ?? cfg.chainId));\n const payload: MintEvent = {\n type,\n state: s.name,\n reason: s.reason,\n chainId: cfg.chainId,\n quantity: s.quantity,\n config: { type: cfg.type, chainId: cfg.chainId, address: eventAddressOf(cfg) },\n route: originChainId === cfg.chainId ? 'direct' : 'relay',\n originChainId,\n ...over,\n };\n // Consumer analytics must not interrupt a transaction or change its outcome.\n try { handler(payload); } catch { /* the host owns its callback errors */ }\n }, []);\n\n // ---------------------------------------------------------------- reads\n\n const clientFor = useCallback((life: Lifetime, cfg: MintConfig): PublicClient => {\n const rpcUrl = propsRef.current.rpcUrls?.[cfg.chainId];\n if (!life.client || life.rpcUrl !== rpcUrl) {\n life.rpcUrl = rpcUrl;\n life.client = createReadClient({ chainId: cfg.chainId, rpcUrls: propsRef.current.rpcUrls, useBridge: bridgeEnabled(cfg) });\n }\n return life.client;\n }, []);\n\n const ctxFor = useCallback(\n (life: Lifetime, cfg: MintConfig): AdapterContext => ({\n client: clientFor(life, cfg),\n cacheScope: life.cacheScope,\n // The lifetime signal: a config change or unmount cancels every adapter call, including shared merkle fetches.\n signal: life.controller.signal,\n }),\n [clientFor],\n );\n\n const readDrop = useCallback(\n async (life: Lifetime, gen: number, cfg: MintConfig): Promise<void> => {\n const adapter = adapterFor(cfg);\n try {\n const drop = await adapter.readDrop(ctxFor(life, cfg), cfg);\n if (life.controller.signal.aborted || life.gen !== gen || lifeRef.current !== life) return;\n dispatch({ type: 'DROP_LOADED', gen, drop, now: Math.max(stateRef.current.now, nowSec()) });\n } catch (e) {\n if (life.controller.signal.aborted || life.gen !== gen || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n if (isUnsupported(reason)) dispatch({ type: 'DROP_UNSUPPORTED', gen, reason, message });\n else dispatch({ type: 'READ_FAILED', gen, ...errorReasonOf(e) });\n }\n },\n [ctxFor, dispatch],\n );\n\n const startLifetime = useCallback(\n (key: string, cfg: MintConfig) => {\n const prev = lifeRef.current;\n lifeRef.current = null;\n prev?.controller.abort();\n const gen = ++genRef.current;\n const life: Lifetime = { key, controller: new AbortController(), cacheScope: {}, gen };\n lifeRef.current = life;\n dispatch({ type: 'LOAD', gen, kind: 'config' });\n void readDrop(life, gen, cfg);\n },\n [dispatch, readDrop],\n );\n\n const refreshNow = useCallback(\n async (kind: 'refresh' | 'config' = 'refresh'): Promise<void> => {\n const life = lifeRef.current;\n if (!life || !mounted.current) return;\n if (connectingRef.current || attemptRef.current || stateRef.current.attempt) {\n pendingRefreshRef.current = true;\n return;\n }\n const gen = ++genRef.current;\n life.gen = gen;\n dispatch({ type: 'LOAD', gen, kind });\n await readDrop(life, gen, propsRef.current.config);\n },\n [dispatch, readDrop],\n );\n\n const refresh = useCallback(() => refreshNow('refresh'), [refreshNow]);\n\n /** True while a click owns the flow: the synchronous claim wins before the reducer has moved. */\n const isBusy = useCallback(\n () => connectingRef.current || attemptRef.current !== null || stateRef.current.attempt !== null || IN_FLIGHT_STATES.includes(stateRef.current.name),\n [],\n );\n\n // ---------------------------------------------------------------- mint\n\n const executeAttempt = useCallback(\n async (attempt: Attempt, walletRead: WalletRead, life: Lifetime, account: Account): Promise<void> => {\n const cfg = attempt.config;\n const adapter = adapterFor(cfg);\n const ctx = ctxFor(life, cfg);\n const client = clientFor(life, cfg);\n\n /** The frozen payer must still be the selected wallet before every prompt. */\n const assertActive = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n };\n const assertPayer = (): void => {\n assertActive();\n const live = getAccount();\n if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {\n throw new MintError('USER_REJECTED', 'The connected wallet changed before the transaction was sent.');\n }\n // The widget's cached chain can lag a successful switch. sendTransaction below\n // verifies the actual provider chain via viem, with the frozen chain bound.\n };\n\n let wallet: WalletClient | null = null;\n const walletFor = (): WalletClient => {\n if (!wallet) wallet = getWalletClient(attempt.chainId);\n return wallet;\n };\n\n const submit = async (tx: TxRequest): Promise<Hex> => {\n assertPayer();\n const w = walletFor() as unknown as {\n sendTransaction: (a: Record<string, unknown>) => Promise<Hex>;\n };\n return w.sendTransaction({ account: attempt.payer, to: tx.to, data: tx.data, value: tx.value, chain: getChain(attempt.chainId) });\n };\n\n const waitReceipt = async (hash: Hex): Promise<Receiptish> => {\n let cancelled = false;\n const params = {\n hash,\n confirmations: 1,\n timeout: 120_000,\n onReplaced: ({ reason }: { reason: string }) => {\n // Only repricing preserves the signed mint intent. Other replacements may succeed without minting.\n if (reason !== 'repriced') cancelled = true;\n },\n };\n let receipt: Receiptish;\n try {\n receipt = await client.waitForTransactionReceipt(params);\n } catch (e) {\n assertActive();\n if (cancelled) throw new MintError('USER_REJECTED', 'The transaction was cancelled or replaced with a different transaction.');\n // WalletClient has no receipt actions until explicitly extended with publicActions.\n if (await walletFor().getChainId() !== attempt.chainId) throw e;\n assertActive();\n receipt = await walletFor().extend(publicActions).waitForTransactionReceipt(params);\n }\n assertActive();\n if (cancelled) throw new MintError('USER_REJECTED', 'The transaction was cancelled or replaced with a different transaction.');\n return receipt;\n };\n\n // 1. wrong chain: switch before anything is signed (AC-4).\n assertPayer();\n if (account.chainId !== attempt.chainId) {\n dispatch({ type: 'SWITCHING_CHAIN', attemptId: attempt.attemptId });\n await switchToChain(attempt.chainId);\n }\n assertPayer();\n\n // 2. plan for the frozen attempt.\n let plan: MintPlan = await adapter.buildPlan(ctx, attempt, walletRead);\n assertPayer();\n\n // 3. ERC20 approval leg, simulated through the read client immediately before the prompt (AC-7).\n if (plan.approve) {\n const a = plan.approve;\n try {\n const simulated = await client.call({ account: attempt.payer, to: a.tx.to, data: a.tx.data, value: a.tx.value });\n if (simulated.data && simulated.data !== '0x' && BigInt(simulated.data) === 0n) {\n throw new MintError('SIMULATION_REVERTED', 'The token refused the approval.');\n }\n } catch (e) {\n if (isMintError(e)) throw e;\n const reverted = e instanceof BaseError ? e.walk(cause => cause instanceof ExecutionRevertedError) : null;\n if (reverted instanceof ExecutionRevertedError) {\n throw new MintError('SIMULATION_REVERTED', reverted.shortMessage);\n }\n throw e;\n }\n assertPayer();\n dispatch({ type: 'APPROVING', attemptId: attempt.attemptId });\n const approveHash = await submit(a.tx);\n assertActive();\n emit('approve-submitted', { state: 'approving', reason: null, txHash: approveHash, quantity: attempt.quantity, chainId: attempt.chainId });\n const approveReceipt = await waitReceipt(approveHash);\n assertActive();\n if (!receiptOk(approveReceipt.status)) {\n throw new MintError('TX_REVERTED', `The approval transaction reverted (${approveHash}).`);\n }\n // Approval does not guarantee the drop is still live or the recipient is still eligible.\n const freshDrop = await adapter.readDrop(ctx, cfg);\n assertActive();\n const freshWallet = await adapter.readWallet(ctx, cfg, {\n payer: attempt.payer,\n recipient: attempt.recipient,\n quantity: attempt.quantity,\n drop: freshDrop,\n });\n assertPayer();\n let checked = reducer(initialState({ quantity: attempt.quantity }), { type: 'ACCOUNT_CHANGED', account });\n checked = reducer(checked, { type: 'DROP_LOADED', gen: checked.readGen, drop: freshDrop, now: nowSec() });\n checked = reducer(checked, { type: 'WALLET_LOADED', gen: checked.walletGen, read: freshWallet });\n if (checked.name !== 'live' || (selectMaxQuantity(checked) ?? 0) < attempt.quantity) {\n throw new MintError(checked.reason ?? 'SIMULATION_REVERTED', checked.message ?? 'The drop is no longer available for this quantity.');\n }\n plan = await adapter.buildPlan(ctx, attempt, freshWallet);\n assertPayer();\n }\n\n // 4. simulate the mint from the payer: a revert never becomes a wallet prompt (AC-13).\n await adapter.simulate(ctx, attempt, plan);\n assertPayer();\n\n // 5. mint prompt.\n dispatch({ type: 'MINT_PROMPTED', attemptId: attempt.attemptId });\n const hash = await submit(plan.mint);\n assertActive();\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('tx-submitted', { state: 'confirming', reason: null, txHash: hash, quantity: attempt.quantity, chainId: attempt.chainId });\n\n const receipt = await waitReceipt(hash);\n assertActive();\n const confirmedHash = receipt.transactionHash ?? hash;\n if (!receiptOk(receipt.status)) {\n throw new MintError('TX_REVERTED', `The mint transaction reverted (${confirmedHash}).`);\n }\n const tokens = parseMintReceipt(receipt.logs ?? [], {\n tokenContract: plan.tokenContract ?? attempt.tokenContract,\n recipient: attempt.recipient,\n });\n const result: MintResult = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens };\n dispatch({ type: 'ATTEMPT_SUCCEEDED', attemptId: attempt.attemptId, result });\n emit('success', { state: 'success', reason: null, txHash: confirmedHash, quantity: attempt.quantity, chainId: attempt.chainId });\n },\n [clientFor, ctxFor, dispatch, emit],\n );\n\n // ---------------------------------------------------------------- relay mint (spec #execute-click)\n\n /** Polls a paid Relay request to a terminal status and settles the attempt (steps 6-8). */\n const finishRelay = useCallback(\n async (attempt: Attempt, out: Outstanding, life: Lifetime): Promise<void> => {\n const cfg = attempt.config;\n const client = clientFor(life, cfg);\n const assertActive = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n };\n const outcome = await pollUntilTerminal({ testnet: out.testnet, requestId: out.requestId, signal: life.controller.signal, assertActive });\n assertActive();\n const where = chainName(out.originChainId);\n if (outcome.kind === 'timeout') {\n // The request stays outstanding: the next click resumes it instead of paying again (R12).\n throw new MintError('BRIDGE_TIMEOUT', 'The bridge is still processing. Your mint may still complete.');\n }\n const key = outstandingKey(attempt.config, attempt.payer);\n const { status } = outcome;\n if (status.status !== 'success') clearOutstanding(key);\n if (status.status === 'refund') {\n throw new MintError('BRIDGE_REFUNDED', `Relay could not complete the mint and is refunding your payment. Relay request ${out.requestId}.`);\n }\n if (status.status === 'failure') {\n const why = status.failReason ? ` (${status.failReason})` : '';\n const noRefund = status.refundFailReason ? ` No automatic refund was sent. Contact support with Relay request ${out.requestId}.` : '';\n throw new MintError('BRIDGE_FAILED', `Relay could not complete the mint paid on ${where}${why}.${noRefund}`);\n }\n // success: the drop-chain receipt is best effort. Once Relay says success, nothing here may produce `error`.\n const fillHash = status.txHashes[0] ?? null;\n let tokens: MintResult['tokens'] = [];\n if (fillHash) {\n try {\n const receipt = (await client.waitForTransactionReceipt({ hash: fillHash, confirmations: 1, timeout: 60_000 })) as Receiptish;\n if (receiptOk(receipt.status)) {\n tokens = parseMintReceipt(receipt.logs ?? [], { tokenContract: attempt.tokenContract, recipient: attempt.recipient });\n }\n } catch {\n /* tokens stay [] */\n }\n assertActive();\n }\n const txHash = fillHash ?? out.originTxHash;\n const result: MintResult = {\n txHash,\n explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),\n tokens,\n relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId },\n };\n // Cleared only now: an unmount during the receipt read leaves it, so a remount resumes and shows success.\n clearOutstanding(key);\n dispatch({ type: 'ATTEMPT_SUCCEEDED', attemptId: attempt.attemptId, result });\n emit('success', { state: 'success', reason: null, txHash, quantity: attempt.quantity, chainId: cfg.chainId });\n },\n [clientFor, dispatch, emit],\n );\n\n const executeRelayAttempt = useCallback(\n async (attempt: Attempt, walletRead: WalletRead, life: Lifetime, account: Account): Promise<void> => {\n const cfg = attempt.config;\n const adapter = adapterFor(cfg);\n const ctx = ctxFor(life, cfg);\n const testnet = isRelayTestnet(cfg.chainId);\n const origin = attempt.originChainId;\n const assertPayer = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n const live = getAccount();\n if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {\n throw new MintError('USER_REJECTED', 'The connected wallet changed before the transaction was sent.');\n }\n };\n\n // 3. only payChainId can put the pay chain somewhere the wallet is not.\n assertPayer();\n if (account.chainId !== origin) {\n dispatch({ type: 'SWITCHING_CHAIN', attemptId: attempt.attemptId });\n await switchToChain(origin);\n }\n assertPayer();\n\n // 4. plan on the drop chain, fresh quote (R6), balance through the wallet (D14). No eth_call simulate (D11).\n const plan = await adapter.buildPlan(ctx, attempt, walletRead);\n if (plan.approve) throw new MintError('BRIDGE_UNAVAILABLE', 'Cross-chain mint supports native-priced drops only.');\n assertPayer();\n const quote: RelayQuote = await getQuote({\n testnet,\n payer: attempt.payer,\n recipient: attempt.recipient,\n originChainId: origin,\n destinationChainId: cfg.chainId,\n tx: plan.mint,\n fresh: true,\n signal: life.controller.signal,\n });\n assertPayer();\n const balance = await getConnectedBalance(attempt.payer, origin);\n if (balance < quote.amountIn.value + quote.originGas.value) {\n const s = shortOf(origin);\n throw new MintError('INSUFFICIENT_FUNDS', `Not enough ${s.symbol} on ${s.name}. Pick another network to pay from.`);\n }\n assertPayer();\n\n // 5. deposit on the origin chain, exactly as Relay specified it.\n // parseQuote guarantees exactly one deposit item on the origin chain.\n const item = quote.items[0]!;\n const key = outstandingKey(cfg, attempt.payer);\n const wallet = getWalletClient(origin);\n assertPayer();\n // Another button for this drop and wallet is prompting or already paid: never a second deposit (R12).\n if (!claimDeposit(key)) {\n throw new MintError('BRIDGE_UNAVAILABLE', 'A cross-chain payment for this drop is already in progress. Try again once it finishes.');\n }\n let out: Outstanding;\n try {\n dispatch({ type: 'MINT_PROMPTED', attemptId: attempt.attemptId });\n const send = wallet as unknown as { sendTransaction: (a: Record<string, unknown>) => Promise<Hex> };\n const originTxHash = await send.sendTransaction({\n account: attempt.payer,\n to: item.to,\n data: item.data,\n value: item.value,\n ...(item.gas !== null && item.gas > 0n ? { gas: item.gas } : {}),\n chain: getChain(origin),\n });\n // Recorded before anything else can throw or unmount: from here on, no second deposit (R12).\n out = { requestId: quote.requestId, testnet, originChainId: origin, originTxHash, attempt };\n setOutstanding(key, out);\n } finally {\n releaseDeposit(key);\n }\n const { originTxHash } = out;\n autoResumedRef.current.add(key);\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('tx-submitted', { state: 'confirming', reason: null, txHash: originTxHash, quantity: attempt.quantity, chainId: cfg.chainId });\n\n // 6. a reverted deposit is final; no receipt in time is not (R9): keep polling Relay.\n const deposit = await waitForDeposit(origin, originTxHash);\n if (deposit === 'reverted') {\n clearOutstanding(key);\n throw new MintError('TX_REVERTED', `The payment transaction reverted (${originTxHash}).`);\n }\n await finishRelay(attempt, out, life);\n },\n [ctxFor, dispatch, emit, finishRelay],\n );\n\n /** Resumes polling a request left outstanding by BRIDGE_TIMEOUT, instead of a new deposit (R12). */\n const resumeOutstanding = useCallback(\n async (out: Outstanding, life: Lifetime): Promise<void> => {\n const attempt: Attempt = Object.freeze({ ...out.attempt, attemptId: ++attemptIdRef.current });\n attemptRef.current = attempt;\n dispatch({ type: 'ATTEMPT_STARTED', attempt, resume: true });\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('attempt', { state: 'confirming', reason: null, quantity: attempt.quantity, chainId: attempt.chainId });\n try {\n await finishRelay(attempt, out, life);\n } catch (e) {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n dispatch({ type: 'ATTEMPT_FAILED', attemptId: attempt.attemptId, reason, message });\n emit('error', { quantity: attempt.quantity, chainId: attempt.chainId });\n } finally {\n attemptRef.current = null;\n }\n },\n [dispatch, emit, finishRelay],\n );\n\n const runMint = useCallback(async (): Promise<void> => {\n if (!mounted.current) return;\n if (attemptRef.current !== null) return; // synchronous guard before any retry or await\n if (widgetFailRef.current) {\n if (isUnsupported(widgetFailRef.current.reason)) return;\n widgetFailRef.current = null;\n setWidgetFail(null);\n setWidgetRetry(n => n + 1);\n void refreshNow('config');\n return;\n }\n\n const s = stateRef.current;\n if (!coreCanAct(s) || s.attempt !== null) return;\n\n // --- connect\n if (s.name === 'disconnected') {\n if (connectingRef.current) return;\n connectingRef.current = true;\n dispatch({ type: 'CONNECT_STARTED' });\n const connectLife = lifeRef.current;\n const active = () => mounted.current && lifeRef.current === connectLife && !connectLife?.controller.signal.aborted;\n try {\n await widgetLoadRef.current;\n if (!active()) return;\n const outcome = await openConnectModal();\n if (!active()) return;\n connectingRef.current = false;\n if (outcome === 'connected') {\n accountKeyRef.current = null;\n pushAccountRef.current?.(getAccount());\n }\n else dispatch({ type: 'CONNECT_DISMISSED' });\n } catch {\n if (active()) dispatch({ type: 'CONNECT_DISMISSED' });\n } finally {\n connectingRef.current = false;\n }\n return;\n }\n\n // --- a paid Relay request for this drop and payer is unresolved: resume it, never pay again (R12).\n // Survives unmount (page memory); a terminal Relay status or a reverted deposit releases it.\n const payer = s.account?.address;\n const out = payer ? getOutstanding(outstandingKey(propsRef.current.config, payer)) : null;\n if (out && lifeRef.current) {\n await resumeOutstanding(out, lifeRef.current);\n return;\n }\n\n // --- reset after a terminal state: a fresh drop + wallet read must land before the button is live again.\n if (s.name === 'success' || s.name === 'error') {\n const life = lifeRef.current;\n const gen = ++genRef.current;\n if (life) life.gen = gen;\n dispatch({ type: 'RESET', gen });\n // Current core RESET retains the old drop. Clear it before the wallet effect can read.\n dispatch({ type: 'LOAD', gen, kind: 'config' });\n if (life) void readDrop(life, gen, propsRef.current.config);\n return;\n }\n\n if (s.name !== 'live') return;\n const account = s.account;\n const walletRead = s.walletRead;\n const life = lifeRef.current;\n if (!account || !walletRead || !life) return;\n\n const cfg = propsRef.current.config;\n const relayRoute = s.route?.kind === 'relay' ? s.route : null;\n const attempt: Attempt = Object.freeze({\n attemptId: ++attemptIdRef.current,\n config: structuredClone(cfg),\n payer: account.address,\n recipient: propsRef.current.recipient ?? account.address,\n quantity: s.quantity,\n chainId: cfg.chainId,\n tokenContract: tokenContractOf(cfg),\n originChainId: relayRoute ? relayRoute.originChainId : cfg.chainId,\n });\n attemptRef.current = attempt;\n dispatch({ type: 'ATTEMPT_STARTED', attempt });\n emit('attempt', { state: 'live', reason: null, quantity: attempt.quantity, chainId: attempt.chainId });\n\n try {\n if (relayRoute) await executeRelayAttempt(attempt, walletRead, life, account);\n else await executeAttempt(attempt, walletRead, life, account);\n } catch (e) {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n dispatch({ type: 'ATTEMPT_FAILED', attemptId: attempt.attemptId, reason, message });\n emit('error', { quantity: attempt.quantity, chainId: attempt.chainId });\n } finally {\n attemptRef.current = null;\n }\n }, [dispatch, emit, readDrop, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);\n\n const mint = useCallback(() => {\n void runMint();\n }, [runMint]);\n\n // ---------------------------------------------------------------- effects (none run on the server)\n\n // Declared first so a StrictMode remount re-arms before the read effects run.\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n lifeRef.current?.controller.abort();\n lifeRef.current = null;\n };\n }, []);\n\n // Account subscription, de-duplicated by address:chainId:recipient so one real change is read once.\n useEffect(() => {\n const push = (a: Account | null): void => {\n const key = a ? `${a.address.toLowerCase()}:${a.chainId}:${(propsRef.current.recipient ?? '').toLowerCase()}` : 'null';\n if (key === accountKeyRef.current) return;\n accountKeyRef.current = key;\n if (attemptRef.current || stateRef.current.attempt) pendingRefreshRef.current = true;\n dispatch({ type: 'ACCOUNT_CHANGED', account: a });\n };\n pushAccountRef.current = push;\n push(getAccount());\n const unsubscribe = subscribeAccount(push);\n return () => {\n unsubscribe();\n };\n }, [dispatch]);\n\n // Recipient change: the same wallet needs a fresh eligibility read for the new recipient.\n const recipientRef = useRef(recipient);\n useEffect(() => {\n if (isBusy() || recipientRef.current === recipient) return;\n recipientRef.current = recipient;\n accountKeyRef.current = null;\n pushAccountRef.current?.(getAccount());\n }, [recipient, machine.attempt, isBusy]);\n\n // Connect widget: the verdict lives outside the reducer and wins over the derived state (AC-20).\n const projectId = connect?.walletConnectProjectId;\n // A wallet on any pay chain must stay connected there, not be forced to the drop chain (spec #connect-widget).\n const widgetChains = [config.chainId, ...payChainsOf(config, options.crossChain).filter((id) => id !== config.chainId)].join(',');\n useEffect(() => {\n let alive = true;\n const loading = loadConnectWidget({ chainIds: widgetChains.split(',').map(Number), ...(projectId ? { walletConnectProjectId: projectId } : {}) });\n widgetLoadRef.current = loading;\n loading.then(\n () => {\n if (alive) setWidgetFail(null);\n },\n (e: unknown) => {\n if (!alive) return;\n const { reason, message } = toReason(e);\n setWidgetFail({ reason, message });\n },\n );\n return () => {\n alive = false;\n };\n }, [widgetChains, projectId, widgetRetry]);\n\n // Read lifetime. No dependency array: a deferred config change is applied on the render after the\n // attempt reaches a terminal state (spec #state-management \"Attempt snapshot\").\n useEffect(() => {\n if (!mounted.current) return;\n if (isBusy()) return; // frozen until terminal\n if (pendingQuantityRef.current !== null) {\n const quantity = pendingQuantityRef.current;\n pendingQuantityRef.current = null;\n dispatch({ type: 'SET_QUANTITY', quantity });\n }\n if (lifeRef.current?.key !== configKey) {\n pendingRefreshRef.current = false;\n startLifetime(configKey, config);\n } else if (pendingRefreshRef.current || lifeRef.current.rpcUrl !== options.rpcUrls?.[config.chainId]) {\n pendingRefreshRef.current = false;\n void refreshNow();\n }\n });\n\n // A paid, unresolved Relay request for this drop and payer (from before an unmount or a reload):\n // pick it straight back up, so the collector sees it confirming instead of a live Mint (R12).\n // Once per key per mount: after that, a timed-out request waits for the collector's click.\n const resumeKey = machine.account ? outstandingKey(config, machine.account.address) : null;\n useEffect(() => {\n if (!resumeKey || autoResumedRef.current.has(resumeKey) || attemptRef.current !== null || isBusy()) return;\n const out = getOutstanding(resumeKey);\n const life = lifeRef.current;\n if (!out || !life) return;\n autoResumedRef.current.add(resumeKey);\n void resumeOutstanding(out, life);\n }, [resumeKey, machine.attempt, isBusy, resumeOutstanding]);\n\n // A different pay-chain override or cross-chain option changes the route: re-read the wallet.\n const routeOptionsKey = `${String(options.payChainId ?? '')}|${JSON.stringify(options.crossChain ?? null)}`;\n const previousRouteOptions = useRef(routeOptionsKey);\n useEffect(() => {\n if (previousRouteOptions.current === routeOptionsKey || isBusy()) return;\n previousRouteOptions.current = routeOptionsKey;\n if (stateRef.current.account) dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }, [routeOptionsKey, machine.attempt, dispatch, isBusy]);\n\n // Quantity changes also change simulation inputs and gas reserve. Invalidate before re-reading.\n const previousQuantity = useRef(machine.quantity);\n useEffect(() => {\n if (previousQuantity.current === machine.quantity || isBusy()) return;\n previousQuantity.current = machine.quantity;\n if (stateRef.current.walletRead) dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }, [machine.quantity, machine.attempt, dispatch, isBusy]);\n\n // Wallet read: bound to the current account, drop and quantity.\n const loadedDrop = machine.drop;\n const walletGen = machine.walletGen;\n const account = machine.account;\n const lastWalletRead = useRef<{ drop: DropInfo; gen: number; quantity: number } | null>(null);\n useEffect(() => {\n if (!account || !loadedDrop || isBusy()) return;\n // A failed preflight remains blocked until its inputs change or a fresh drop arrives.\n const previous = lastWalletRead.current;\n if (stateRef.current.blocked && previous?.drop === loadedDrop && previous.gen === walletGen && previous.quantity === machine.quantity) return;\n lastWalletRead.current = { drop: loadedDrop, gen: walletGen, quantity: machine.quantity };\n const life = lifeRef.current;\n const drop = stateRef.current.drop;\n if (!life || !drop || life.key !== configKeyOf(propsRef.current.config)) return;\n const readGen = life.gen;\n const cfg = propsRef.current.config;\n const adapter = adapterFor(cfg);\n let alive = true;\n void (async () => {\n try {\n const read = await adapter.readWallet(ctxFor(life, cfg), cfg as never, {\n payer: account.address,\n recipient: propsRef.current.recipient ?? account.address,\n quantity: stateRef.current.quantity,\n drop,\n });\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;\n // A preflight revert arrives as data, not a thrown error, so it gets the same public text here.\n const ineligible = read.ineligible?.reason === 'SIMULATION_REVERTED'\n ? { ...read.ineligible, message: friendlyErrorMessage(new MintError('SIMULATION_REVERTED', read.ineligible.message)) }\n : read.ineligible;\n const fullRead = { ...read, ineligible };\n\n // Relay (spec #route-read-idle): only the wallet's own chain is ever quoted or balance-checked (D14).\n const payChains = payChainsOf(cfg, propsRef.current.crossChain);\n const crossChain = payChains.length > 0 && ineligible === null && (adapter.supportsRelay?.(drop) ?? false);\n const routeChain = routeChainOf(payChains, account.chainId, propsRef.current.payChainId);\n const routePending = crossChain && routeChain !== cfg.chainId && payChains.includes(routeChain);\n dispatch({ type: 'WALLET_LOADED', gen: walletGen, read: fullRead, crossChain, routePending });\n if (!crossChain) {\n setCrossChainInfo(null);\n return;\n }\n\n const recipientAddr = propsRef.current.recipient ?? account.address;\n const draft: Attempt = {\n attemptId: 0,\n config: cfg,\n payer: account.address,\n recipient: recipientAddr,\n quantity: stateRef.current.quantity,\n chainId: cfg.chainId,\n tokenContract: tokenContractOf(cfg),\n originChainId: routeChain,\n };\n const testnet = isRelayTestnet(cfg.chainId);\n let tx: TxRequest;\n try {\n tx = (await adapter.buildPlan(ctxFor(life, cfg), draft, fullRead)).mint;\n } catch {\n if (!alive) return;\n setCrossChainInfo(null);\n if (routePending) dispatch({ type: 'ROUTE_LOADED', gen: walletGen, route: { kind: 'direct', chainId: cfg.chainId }, short: null });\n return;\n }\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n setCrossChainInfo((prev) => {\n const same = prev && prev.info.dropChainId === cfg.chainId && prev.info.testnet === testnet && prev.info.tx.to === tx.to\n && prev.info.tx.data === tx.data && prev.info.tx.value === tx.value && prev.info.payer === account.address\n && prev.info.recipient === recipientAddr && prev.info.payChains.join() === payChains.join();\n // Keep the same object when nothing changed, so the picker does not re-quote on a refresh.\n return { gen: walletGen, info: same ? prev.info : { dropChainId: cfg.chainId, payChains, tx, payer: account.address, recipient: recipientAddr, testnet } };\n });\n if (!routePending) return;\n\n // Debounce: quantity steps and refreshes supersede this read through `alive`.\n await new Promise((r) => setTimeout(r, ROUTE_DEBOUNCE_MS));\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n let route: Route = { kind: 'direct', chainId: cfg.chainId };\n let short: PayShort | null = null;\n try {\n const quote = await getQuote({ testnet, payer: account.address, recipient: recipientAddr, originChainId: routeChain, destinationChainId: cfg.chainId, tx, signal: life.controller.signal });\n const relay: Route = { kind: 'relay', originChainId: routeChain, amountIn: quote.amountIn, relayFee: quote.relayFee, originGas: quote.originGas, quotedAt: quote.quotedAt };\n if (routeChain !== account.chainId) {\n route = relay; // payChainId forced: the click switches and checks the balance there.\n } else {\n const balance = await getConnectedBalance(account.address, routeChain);\n if (balance >= quote.amountIn.value + quote.originGas.value) route = relay;\n else short = shortOf(routeChain);\n }\n } catch {\n // Any quote or balance failure only removes the Relay route (N3).\n }\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n dispatch({ type: 'ROUTE_LOADED', gen: walletGen, route: short ? null : route, short });\n } catch (e) {\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;\n dispatch({ type: 'WALLET_FAILED', gen: walletGen, ...errorReasonOf(e) });\n }\n })();\n return () => {\n alive = false;\n };\n }, [account, loadedDrop, walletGen, machine.quantity, machine.attempt, ctxFor, dispatch, isBusy]);\n\n // Controlled quantity: the prop is the source of truth.\n const prevQuantityProp = useRef(quantityProp);\n useEffect(() => {\n if (isBusy()) return;\n const propChanged = prevQuantityProp.current !== quantityProp;\n prevQuantityProp.current = quantityProp;\n if (quantityProp === undefined || (!propChanged && !propsRef.current.onQuantityChange)) return;\n const state = stateRef.current;\n // Do not oscillate between the supply cap and a tighter wallet cap while wallet reads are pending.\n if (!propChanged && state.account && !state.walletRead) return;\n const event = { type: 'SET_QUANTITY' as const, quantity: quantityProp };\n const clamped = reducer(state, event).quantity;\n if (clamped !== state.quantity) dispatch(event);\n if (clamped !== quantityProp) propsRef.current.onQuantityChange?.(clamped);\n }, [quantityProp, machine.quantity, machine.drop, machine.walletRead, machine.attempt, dispatch, isBusy]);\n\n // Freshness: re-read every 30s and on visibilitychange (spec #state-management).\n useEffect(() => {\n const timer = setInterval(() => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;\n void refreshNow('refresh');\n }, REFRESH_MS);\n const onVisible = (): void => {\n if (document.visibilityState === 'visible') void refreshNow('refresh');\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => {\n clearInterval(timer);\n document.removeEventListener('visibilitychange', onVisible);\n };\n }, [refreshNow]);\n\n // Clock: only runs while a start/end boundary is still ahead, so an idle button never re-renders.\n const boundary = machine.drop\n ? [machine.drop.startTime, machine.drop.endTime].some((t) => t !== null && t > machine.now)\n : false;\n useEffect(() => {\n if (!boundary) return;\n const timer = setInterval(() => {\n const s = stateRef.current;\n const now = nowSec();\n const crossed = s.drop && [s.drop.startTime, s.drop.endTime].some(t => t !== null && t > s.now && t <= now);\n dispatch({ type: 'TICK', now });\n if (crossed) void refreshNow();\n }, TICK_MS);\n return () => clearInterval(timer);\n }, [boundary, dispatch, refreshNow]);\n\n // 'state' events (spec #package-api onEvent payload).\n const prevName = useRef<MintStateName | null>(null);\n useEffect(() => {\n const name = widgetFail ? (isUnsupported(widgetFail.reason) ? 'unsupported' : 'error') : machine.name;\n if (prevName.current === name) return;\n prevName.current = name;\n emit('state', { state: name, reason: widgetFail?.reason ?? machine.reason });\n }, [machine.name, machine.reason, widgetFail, emit]);\n\n // ---------------------------------------------------------------- public state\n\n const setQuantity = useCallback(\n (next: number) => {\n const s = stateRef.current;\n const max = selectMaxQuantity(s) ?? 65535;\n const clamped = Math.max(1, Math.min(Math.floor(Number.isNaN(next) ? 1 : next), max));\n const handler = propsRef.current.onQuantityChange;\n if (handler) {\n handler(clamped); // controlled: the prop drives m.quantity\n return;\n }\n if (attemptRef.current) {\n pendingQuantityRef.current = clamped;\n return;\n }\n dispatch({ type: 'SET_QUANTITY', quantity: clamped });\n if (s.quantity !== stateRef.current.quantity) {\n dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }\n },\n [dispatch],\n );\n\n\n const effective: Pick<MachineState, 'name' | 'reason' | 'message'> = widgetFail\n ? { name: isUnsupported(widgetFail.reason) ? 'unsupported' : 'error', reason: widgetFail.reason, message: widgetFail.message }\n : { name: machine.name, reason: machine.reason, message: machine.message };\n\n const wallet: WalletInfo | null =\n machine.account && machine.walletRead\n ? {\n address: machine.account.address,\n chainId: machine.account.chainId,\n recipient: recipient ?? machine.account.address,\n walletMinted: machine.walletRead.walletMinted,\n walletMax: machine.walletRead.walletMax,\n allowlistRemaining: machine.walletRead.allowlistRemaining,\n }\n : null;\n\n return {\n state: effective.name,\n reason: effective.reason,\n message: effective.message,\n drop: machine.drop,\n wallet,\n quantity: machine.quantity,\n setQuantity,\n maxQuantity: selectMaxQuantity(machine),\n total: selectTotal(machine),\n mint,\n result: machine.result,\n refresh,\n // An attempt claimed by a click is not clickable, even while the reducer still reads 'live'.\n canAct: coreCanAct({ ...machine, name: effective.name }) && machine.attempt === null,\n // Short on the wallet chain is neither route: nothing can be paid until the collector picks another chain.\n route: machine.route ?? (machine.walletRead && machine.drop && !machine.routePending && machine.short === null ? { kind: 'direct', chainId: machine.drop.chainId } : null),\n crossChain: machine.crossChain && machine.account && crossChainInfo?.gen === machine.walletGen ? crossChainInfo.info : null,\n };\n}\n","// Relay cross-chain mint: raw HTTP client. Spec: docs/specs/relay-cross-chain-mint.html #relay-client.\n// No SDK, no API key, no referrer (R10): Relay 401s any quote that carries a referrer without a key.\nimport { formatUnits, getAddress, isHex, keccak256, zeroAddress } from 'viem';\nimport type { Address, Hex, Money } from '../types';\nimport type { TxRequest } from '../adapters/types';\nimport { MintError } from '../errors';\n\nexport const RELAY_API = { mainnet: 'https://api.relay.link', testnet: 'https://api.testnets.relay.link' } as const;\n\n/** Drop chains Relay can mint on, and the chains a collector may pay from for each (R2). */\nconst MAINNET_PAY_CHAINS = [1, 8453, 10, 360, 33139] as const;\nconst MAINNET_DROP_CHAINS: readonly number[] = [1, 8453, 10, 360];\nconst SEPOLIA = 11155111;\nconst BASE_SEPOLIA = 84532;\n\nexport const isRelayTestnet = (dropChainId: number): boolean => dropChainId === SEPOLIA;\n\n/** Pay chains for a drop chain, the drop chain included. Empty when Relay cannot mint there. */\nexport function payChainsFor(dropChainId: number): number[] {\n if (MAINNET_DROP_CHAINS.includes(dropChainId)) return [...MAINNET_PAY_CHAINS];\n if (dropChainId === SEPOLIA) return [SEPOLIA, BASE_SEPOLIA];\n return [];\n}\n\nexport type RelayTxItem = { chainId: number; to: Address; data: Hex; value: bigint; gas: bigint | null };\n\nexport type RelayQuote = {\n requestId: Hex;\n originChainId: number;\n items: RelayTxItem[];\n amountIn: Money;\n relayFee: Money;\n originGas: Money;\n quotedAt: number;\n};\n\nexport type QuoteArgs = {\n testnet: boolean;\n payer: Address;\n recipient: Address;\n originChainId: number;\n destinationChainId: number;\n tx: TxRequest;\n signal?: AbortSignal;\n fetch?: typeof fetch;\n /** Skip the 30s cache (click-time quotes, R6). */\n fresh?: boolean;\n};\n\nconst CACHE_MS = 30_000;\nconst cache = new Map<string, { at: number; quote: Promise<RelayQuote> }>();\n\nconst cacheKey = (a: QuoteArgs): string =>\n [a.testnet, a.originChainId, a.destinationChainId, a.tx.to.toLowerCase(), a.tx.value, keccak256(a.tx.data), a.payer.toLowerCase(), a.recipient.toLowerCase()].join(':');\n\n/** Test hook: drop every cached quote. */\nexport function clearQuoteCache(): void {\n cache.clear();\n}\n\nconst big = (v: unknown): bigint => {\n if (typeof v === 'string' && /^\\d+$/.test(v)) return BigInt(v);\n if (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0) return BigInt(v);\n return 0n;\n};\n\ntype Json = Record<string, unknown>;\nconst obj = (v: unknown): Json => (v !== null && typeof v === 'object' ? (v as Json) : {});\n\nfunction moneyOf(amount: unknown, currency: unknown): Money {\n const c = obj(currency);\n const decimals = typeof c.decimals === 'number' ? c.decimals : 18;\n const symbol = typeof c.symbol === 'string' ? c.symbol : 'ETH';\n const value = big(amount);\n return { value, decimals, symbol, formatted: formatUnits(value, decimals) };\n}\n\n/** Strips viem/Relay's \"execution reverted: \" prefix; empty when no reason was given. */\nfunction revertReason(errorData: unknown): string {\n if (typeof errorData !== 'string') return '';\n const text = errorData.replace(/^execution reverted:?\\s*/i, '').trim();\n return text === 'execution reverted' ? '' : text;\n}\n\n/** Response body -> RelayQuote, rejecting any step Dropkit does not know how to execute. */\nexport function parseQuote(body: unknown, originChainId: number): RelayQuote {\n const b = obj(body);\n const steps = Array.isArray(b.steps) ? b.steps.map(obj) : [];\n const unknownStep = () => new MintError('BRIDGE_UNAVAILABLE', 'Relay returned a route this button cannot execute.');\n if (steps.length !== 1 || steps[0]!.kind !== 'transaction') throw unknownStep();\n const step = steps[0]!;\n const rawItems = Array.isArray(step.items) ? step.items.map(obj) : [];\n // Native -> native is one deposit. More items would leave a paid-but-untracked gap between prompts.\n if (rawItems.length !== 1) throw unknownStep();\n const items = rawItems.map((item): RelayTxItem => {\n const d = obj(item.data);\n const chainId = Number(d.chainId);\n if (chainId !== originChainId || typeof d.to !== 'string' || !isHex(d.data)) throw unknownStep();\n return { chainId, to: getAddress(d.to), data: d.data, value: big(d.value), gas: d.gas === undefined ? null : big(d.gas) };\n });\n const requestId = (isHex(step.requestId) ? step.requestId : isHex(b.requestId) ? b.requestId : null) as Hex | null;\n if (!requestId) throw unknownStep();\n const details = obj(b.details);\n const currencyIn = obj(details.currencyIn);\n const fees = obj(b.fees);\n const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);\n if (obj(currencyIn.currency).address !== undefined && obj(currencyIn.currency).address !== zeroAddress) throw unknownStep();\n // The wallet sends the item's value; it must be exactly what the label and the funds check used.\n if (items[0]!.value !== amountIn.value) throw unknownStep();\n return {\n requestId,\n originChainId,\n items,\n amountIn,\n relayFee: moneyOf(obj(fees.relayer).amount, obj(fees.relayer).currency ?? currencyIn.currency),\n originGas: moneyOf(obj(fees.gas).amount, obj(fees.gas).currency ?? currencyIn.currency),\n quotedAt: Math.floor(Date.now() / 1000),\n };\n}\n\n/** Bounds on every Relay HTTP call, so a hung API can never wedge the button (N3). Mutable for tests. */\nexport const RELAY_TIMEOUTS = { quoteMs: 10_000, statusMs: 10_000 };\n\n/** Rejects with AbortError when `signal` aborts; otherwise settles with `p`. */\nfunction raceSignal<T>(p: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return p;\n if (signal.aborted) return Promise.reject(new DOMException('Relay quote cancelled', 'AbortError'));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(new DOMException('Relay quote cancelled', 'AbortError'));\n signal.addEventListener('abort', onAbort, { once: true });\n p.then(\n (v) => {\n signal.removeEventListener('abort', onAbort);\n resolve(v);\n },\n (e: unknown) => {\n signal.removeEventListener('abort', onAbort);\n reject(e instanceof Error ? e : new Error(String(e)));\n },\n );\n });\n}\n\n/**\n * fetch + body read under one timeout (a body that never finishes must not hang the button); never the\n * caller's signal, because the cached request is shared.\n */\nasync function timedFetch(f: typeof fetch, url: string, init: RequestInit, ms: number): Promise<{ ok: boolean; status: number; json: unknown }> {\n const controller = new AbortController();\n let t: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n t = setTimeout(() => {\n controller.abort();\n reject(new DOMException('Relay request timed out', 'TimeoutError'));\n }, ms);\n });\n try {\n return await Promise.race([\n (async () => {\n const res = await f(url, { ...init, signal: controller.signal });\n const json: unknown = await res.json().catch(() => null);\n return { ok: res.ok, status: res.status, json };\n })(),\n timeout,\n ]);\n } finally {\n clearTimeout(t);\n }\n}\n\nasync function requestQuote(a: QuoteArgs): Promise<RelayQuote> {\n const f = a.fetch ?? globalThis.fetch;\n const body = {\n user: a.payer,\n recipient: a.recipient,\n refundTo: a.payer, // R11: without refundTo, Relay disables automatic refunds.\n originChainId: a.originChainId,\n destinationChainId: a.destinationChainId,\n originCurrency: zeroAddress,\n destinationCurrency: zeroAddress,\n tradeType: 'EXACT_OUTPUT',\n amount: a.tx.value.toString(),\n txs: [{ to: a.tx.to, value: a.tx.value.toString(), data: a.tx.data }],\n };\n let res: { ok: boolean; status: number; json: unknown };\n try {\n res = await timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/quote`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, RELAY_TIMEOUTS.quoteMs);\n } catch (e) {\n throw new MintError('BRIDGE_UNAVAILABLE', 'Could not reach Relay for a cross-chain quote.', { cause: e });\n }\n const json = res.json;\n if (res.ok) return parseQuote(json, a.originChainId);\n const j = obj(json);\n if (j.errorCode === 'DESTINATION_TX_FAILED') {\n const reason = revertReason(j.errorData);\n throw new MintError('SIMULATION_REVERTED', reason || 'The mint would fail on the drop network.', { cause: json, domain: 'relay' });\n }\n if (res.status === 401 || j.errorCode === 'UNAUTHORIZED_QUOTE') {\n console.warn('dropkit: Relay rejected the quote (401 UNAUTHORIZED_QUOTE). Relay gates quotes that carry a referrer or need an API key.');\n throw new MintError('BRIDGE_UNAVAILABLE', 'Relay refused the cross-chain quote.', { cause: json });\n }\n if (res.status === 429) throw new MintError('RATE_LIMITED', 'Relay is rate limiting quotes. Try again in a moment.', { cause: json });\n throw new MintError('BRIDGE_UNAVAILABLE', `Relay could not quote this mint (${String(res.status)}).`, { cause: json });\n}\n\n/** POST /quote, EXACT_OUTPUT native -> native with plan.mint as the destination call. Cached 30s unless fresh. */\nexport function getQuote(a: QuoteArgs): Promise<RelayQuote> {\n const k = cacheKey(a);\n const now = Date.now();\n const hit = cache.get(k);\n if (!a.fresh && hit && now - hit.at < CACHE_MS) return raceSignal(hit.quote, a.signal);\n const quote = requestQuote(a);\n cache.set(k, { at: now, quote });\n quote.catch(() => {\n if (cache.get(k)?.quote === quote) cache.delete(k);\n });\n // Each caller may give up on its own; the shared request keeps going for the others.\n return raceSignal(quote, a.signal);\n}\n\nexport type RelayStatus = {\n status: string;\n txHashes: Hex[];\n failReason: string | null;\n refundFailReason: string | null;\n};\n\nconst realReason = (v: unknown): string | null => (typeof v === 'string' && v !== '' && v !== 'N/A' ? v : null);\n\n/** GET /intents/status/v3. The quote's check.endpoint is v1 and is ignored (D9). */\nexport async function getStatus(a: { testnet: boolean; requestId: Hex; signal?: AbortSignal; fetch?: typeof fetch }): Promise<RelayStatus> {\n const f = a.fetch ?? globalThis.fetch;\n const res = await raceSignal(timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/intents/status/v3?requestId=${a.requestId}`, {}, RELAY_TIMEOUTS.statusMs), a.signal);\n if (!res.ok) throw new Error(`Relay status ${String(res.status)}`);\n const j = obj(res.json);\n return {\n status: typeof j.status === 'string' ? j.status : 'unknown',\n txHashes: Array.isArray(j.txHashes) ? j.txHashes.filter((h): h is Hex => isHex(h)) : [],\n failReason: realReason(j.failReason),\n refundFailReason: realReason(j.refundFailReason),\n };\n}\n\nexport const KEEP_POLLING: readonly string[] = ['waiting', 'depositing', 'pending', 'submitted', 'delayed'];\nexport const TERMINAL: readonly string[] = ['success', 'refund', 'failure'];\n","// Polls Relay's v3 status until a terminal status or the cap (spec #execute-click steps 6-8).\nimport type { Hex } from '../types';\nimport { KEEP_POLLING, TERMINAL, getStatus, type RelayStatus } from './client';\n\nexport const POLL_FAST_MS = 2_000;\nexport const POLL_SLOW_MS = 5_000;\nexport const POLL_FAST_FOR_MS = 60_000;\nexport const POLL_CAP_MS = 10 * 60_000;\n\nexport type PollOutcome = { kind: 'terminal'; status: RelayStatus } | { kind: 'timeout' };\n\nexport type PollArgs = {\n testnet: boolean;\n requestId: Hex;\n signal?: AbortSignal;\n fetch?: typeof fetch;\n /** Injected for tests. */\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n now?: () => number;\n /** Throws to stop polling (the attempt was cancelled). */\n assertActive?: () => void;\n};\n\nconst defaultSleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('Relay polling cancelled', 'AbortError'));\n }, { once: true });\n });\n\n/**\n * Polls until success / refund / failure, or POLL_CAP_MS. Every other status, `unknown` and v1's\n * `fallback` included, keeps polling (warned once). HTTP errors and 429s also keep polling: once the\n * deposit exists, only Relay's terminal status may end the attempt (R9).\n */\nexport async function pollUntilTerminal(a: PollArgs): Promise<PollOutcome> {\n const sleep = a.sleep ?? defaultSleep;\n const now = a.now ?? Date.now;\n const start = now();\n let warned = false;\n for (;;) {\n a.assertActive?.();\n try {\n const status = await getStatus({ testnet: a.testnet, requestId: a.requestId, ...(a.signal ? { signal: a.signal } : {}), ...(a.fetch ? { fetch: a.fetch } : {}) });\n if (TERMINAL.includes(status.status)) return { kind: 'terminal', status };\n if (!KEEP_POLLING.includes(status.status) && !warned) {\n warned = true;\n console.warn(`dropkit: Relay status \"${status.status}\" for ${a.requestId}; still waiting.`);\n }\n } catch (e) {\n if ((e as { name?: string } | null)?.name === 'AbortError') throw e;\n // Transient: keep polling.\n }\n a.assertActive?.();\n const elapsed = now() - start;\n if (elapsed >= POLL_CAP_MS) return { kind: 'timeout' };\n await sleep(elapsed < POLL_FAST_FOR_MS ? POLL_FAST_MS : POLL_SLOW_MS, a.signal);\n }\n}\n","// Paid-but-unresolved Relay requests (spec R12), in memory for this page. After BRIDGE_TIMEOUT the next\n// click (or a remounted button) resumes the same request instead of paying again. Released by a\n// terminal Relay status or a reverted deposit. A reload forgets it, same as claim-widgets (accepted).\nimport type { Address, Attempt, Hex, MintConfig } from '../types';\n\nexport type Outstanding = {\n requestId: Hex;\n testnet: boolean;\n originChainId: number;\n originTxHash: Hex;\n /** The frozen attempt that paid. A resume re-uses it verbatim. */\n attempt: Attempt;\n};\n\nconst memory = new Map<string, Outstanding>();\n/** Keys with a Relay deposit being prompted right now, across every button on the page. */\nconst claimed = new Set<string>();\n\n/** One drop, one payer. Recipient and quantity are deliberately not part of the key. */\nexport function outstandingKey(config: MintConfig, payer: Address): string {\n const target = config.type === 'manifold-claim' ? `${config.creatorContract.toLowerCase()}:${config.instanceId}` : config.address.toLowerCase();\n return `${String(config.chainId)}:${target}:${payer.toLowerCase()}`;\n}\n\nexport const getOutstanding = (key: string): Outstanding | null => memory.get(key) ?? null;\nexport const setOutstanding = (key: string, out: Outstanding): void => void memory.set(key, out);\nexport const clearOutstanding = (key: string): void => void memory.delete(key);\n\n/** One deposit prompt per drop and payer at a time: two buttons for the same drop must not both pay. */\nexport function claimDeposit(key: string): boolean {\n if (claimed.has(key) || memory.has(key)) return false;\n claimed.add(key);\n return true;\n}\n\nexport const releaseDeposit = (key: string): void => void claimed.delete(key);\n\n/** Test hook: forget every request. */\nexport function resetOutstandingForTests(): void {\n memory.clear();\n claimed.clear();\n}\n","// Interface contract (orchestrator-owned). Implementation: story s6. Spec ContractConfig.\nimport {\n BaseError,\n ContractFunctionRevertedError,\n ContractFunctionZeroDataError,\n InsufficientFundsError,\n decodeErrorResult,\n encodeFunctionData,\n formatUnits,\n getAddress,\n isAddress,\n parseAbi,\n parseUnits,\n type Abi,\n type AbiFunction,\n type AbiParameter,\n type Hex,\n} from 'viem';\nimport type { Address, ArgTemplate, Attempt, ContractConfig, DropInfo, FieldSource, Money } from '../../types';\nimport type { AdapterContext, MintAdapter, MintPlan, WalletRead } from '../types';\nimport { MintError, isMintError } from '../../errors';\nimport { MAX_QUANTITY } from '../../machine';\n\nexport const PLACEHOLDERS = ['{recipient}', '{payer}', '{quantity}', '{unitPrice}', '{totalPrice}'] as const;\n\ntype Placeholder = (typeof PLACEHOLDERS)[number];\n\nconst ERC20_ABI = parseAbi([\n 'function symbol() view returns (string)',\n 'function decimals() view returns (uint8)',\n 'function balanceOf(address) view returns (uint256)',\n 'function allowance(address,address) view returns (uint256)',\n 'function approve(address,uint256) returns (bool)',\n]);\n\n/** Gas units kept aside when the mint cannot be estimated (ERC20-priced mints, estimate failure). */\nconst FALLBACK_MINT_GAS = 300_000n;\n/** Extra gas for the approve that precedes an ERC20-priced mint. */\nconst APPROVE_GAS = 65_000n;\n\nconst PLACEHOLDER_TOKEN = /\\{[A-Za-z_]\\w*\\}/g;\nconst DISPLAY_AMOUNT = /^\\d+(\\.\\d+)?$/;\nconst NUMERIC_TYPE = /^u?int\\d*$/;\n\n// ---------- validation ----------\n\nfunction bad(field: string, message: string): never {\n throw new MintError('INVALID_CONFIG', `${field} ${message}`, { field });\n}\n\nconst isHexAddress = (a: unknown): a is Address => typeof a === 'string' && isAddress(a, { strict: false });\n\nfunction functionsNamed(abi: Abi, name: string): AbiFunction[] {\n return abi.filter((i): i is AbiFunction => i.type === 'function' && i.name === name);\n}\n\nfunction validateTemplate(t: unknown, path: string, allowed: readonly Placeholder[]): void {\n if (typeof t === 'string') {\n for (const token of t.match(PLACEHOLDER_TOKEN) ?? []) {\n if (!(PLACEHOLDERS as readonly string[]).includes(token)) bad(path, `\"${t}\" is not a known placeholder (${token})`);\n if (!allowed.includes(token as Placeholder)) bad(path, `\"${t}\" uses ${token}, which is not available in this field`);\n }\n } else if (Array.isArray(t)) {\n t.forEach((x, i) => validateTemplate(x, `${path}[${i}]`, allowed));\n } else if (typeof t === 'number') {\n if (!Number.isSafeInteger(t)) bad(path, `${t} is not a safe integer; pass larger or fractional values as a bigint or decimal string`);\n } else if (typeof t !== 'bigint' && typeof t !== 'boolean') {\n bad(path, 'must be a string, number, bigint, boolean or array');\n }\n}\n\n// ---------- template vs ABI input types ----------\n\ntype Issue = { path: string; message: string };\n\nconst ARRAY_TYPE = /^(.*)\\[(\\d*)\\]$/;\nconst BYTES_TYPE = /^bytes(\\d*)$/;\nconst INT_TYPE = /^(u?)int(\\d*)$/;\nconst isExactPlaceholder = (t: unknown): t is Placeholder => typeof t === 'string' && (PLACEHOLDERS as readonly string[]).includes(t);\nconst hasPlaceholder = (t: unknown): boolean => typeof t === 'string' && (t.match(PLACEHOLDER_TOKEN) ?? []).length > 0;\n\nfunction intBounds(type: string): { min: bigint; max: bigint } | null {\n const m = INT_TYPE.exec(type);\n if (!m) return null;\n const bits = BigInt(m[2] ? Number(m[2]) : 256);\n return m[1] ? { min: 0n, max: 2n ** bits - 1n } : { min: -(2n ** (bits - 1n)), max: 2n ** (bits - 1n) - 1n };\n}\n\nconst elementParam = (p: AbiParameter, type: string): AbiParameter => ({ ...p, type }) as AbiParameter;\nconst componentsOf = (p: AbiParameter): readonly AbiParameter[] => (p as { components?: readonly AbiParameter[] }).components ?? [];\n\n/** First way template `t` cannot be encoded as ABI input `p`, or null when it fits. */\nfunction typeIssue(t: ArgTemplate, p: AbiParameter, path: string): Issue | null {\n const no = (message: string): Issue => ({ path, message });\n const arr = ARRAY_TYPE.exec(p.type);\n if (arr) {\n if (!Array.isArray(t)) return no(`must be an array for ${p.type}`);\n if (arr[2] !== '' && t.length !== Number(arr[2])) return no(`has ${t.length} item(s) but ${p.type} takes ${arr[2]}`);\n const el = elementParam(p, arr[1]!);\n for (let i = 0; i < t.length; i++) {\n const issue = typeIssue(t[i]!, el, `${path}[${i}]`);\n if (issue) return issue;\n }\n return null;\n }\n if (p.type === 'tuple') {\n const comps = componentsOf(p);\n if (!Array.isArray(t)) return no('must be an array for a tuple');\n if (t.length !== comps.length) return no(`has ${t.length} item(s) but the tuple takes ${comps.length}`);\n for (let i = 0; i < comps.length; i++) {\n const issue = typeIssue(t[i]!, comps[i]!, `${path}[${i}]`);\n if (issue) return issue;\n }\n return null;\n }\n if (Array.isArray(t)) return no(`must not be an array for ${p.type}`);\n if (p.type === 'address') {\n if (isExactPlaceholder(t)) return t === '{recipient}' || t === '{payer}' ? null : no(`${t} is a number, but the abi input is an address`);\n return typeof t === 'string' && !hasPlaceholder(t) && isAddress(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);\n }\n if (p.type === 'bool') return typeof t === 'boolean' ? null : no(`${JSON.stringify(String(t))} is not a boolean`);\n if (p.type === 'string') return typeof t === 'string' ? null : no(`${JSON.stringify(String(t))} is not a string`);\n const bytes = BYTES_TYPE.exec(p.type);\n if (bytes) {\n const ok = typeof t === 'string' && !hasPlaceholder(t) && /^0x([0-9a-fA-F]{2})*$/.test(t) && (!bytes[1] || t.length === 2 + 2 * Number(bytes[1]));\n return ok ? null : no(`${JSON.stringify(String(t))} is not ${bytes[1] ? `${bytes[1]} bytes of` : ''} hex data`);\n }\n const bounds = intBounds(p.type);\n if (bounds) {\n let n: bigint;\n if (isExactPlaceholder(t)) return t === '{recipient}' || t === '{payer}' ? no(`${t} is an address, but the abi input is ${p.type}`) : null;\n if (typeof t === 'bigint') n = t;\n else if (typeof t === 'number') n = BigInt(t);\n else if (typeof t === 'string' && !hasPlaceholder(t) && /^-?\\d+$/.test(t)) n = BigInt(t);\n else return no(`${JSON.stringify(String(t))} is not an integer for ${p.type}`);\n return n >= bounds.min && n <= bounds.max ? null : no(`${n} is out of range for ${p.type}`);\n }\n return no(`abi type ${p.type} is not supported in an argument template`);\n}\n\nconst signature = (f: AbiFunction): string => `${f.name}(${f.inputs.map((i) => i.type).join(',')})`;\n\n/** Placeholders that exist before a wallet is connected. */\nconst NO_PLACEHOLDERS: readonly Placeholder[] = [];\n\n/** Resolves one ABI function by name and by input types compatible with the arg templates. */\nfunction validateCall(\n abi: Abi,\n call: unknown,\n prefix: string,\n kind: 'read' | 'mint',\n allowed: readonly Placeholder[],\n): AbiFunction {\n const c = (call ?? {}) as { functionName?: unknown; args?: unknown };\n const named = typeof c.functionName === 'string' ? functionsNamed(abi, c.functionName) : [];\n if (named.length === 0) bad(`${prefix}.functionName`, `\"${String(c.functionName)}\" is not a function in the abi`);\n if (c.args !== undefined && !Array.isArray(c.args)) bad(`${prefix}.args`, 'must be an array');\n if (kind === 'mint' && c.args === undefined) bad(`${prefix}.args`, 'is required');\n const args = (c.args ?? []) as unknown[];\n args.forEach((a, i) => validateTemplate(a, `${prefix}.args[${i}]`, allowed));\n const sameArity = named.filter((f) => f.inputs.length === args.length);\n if (sameArity.length === 0) {\n const want = [...new Set(named.map((f) => f.inputs.length))].join(' or ');\n bad(`${prefix}.args`, `has ${args.length} argument(s) but \"${String(c.functionName)}\" takes ${want}`);\n }\n const checked = sameArity.map((f) => {\n let issue: Issue | null = null;\n for (let i = 0; i < args.length && !issue; i++) issue = typeIssue(args[i] as ArgTemplate, f.inputs[i]!, `${prefix}.args[${i}]`);\n return { f, issue };\n });\n const fits = checked.filter((x) => !x.issue);\n if (fits.length === 0) {\n const first = checked[0]!.issue!;\n if (sameArity.length === 1) bad(first.path, first.message);\n bad(`${prefix}.args`, `fit none of the overloads ${sameArity.map(signature).join(', ')}: ${first.path} ${first.message}`);\n }\n const roleFits = fits.filter(({ f }) =>\n kind === 'read'\n ? f.stateMutability === 'view' || f.stateMutability === 'pure'\n : f.stateMutability === 'payable' || f.stateMutability === 'nonpayable',\n );\n if (roleFits.length === 0) {\n const name = fits[0]!.f.name;\n if (kind === 'read') bad(`${prefix}.functionName`, `\"${name}\" is not a view or pure function`);\n if (fits.some(({ f }) => f.stateMutability !== 'view' && f.stateMutability !== 'pure')) {\n bad(`${prefix}.functionName`, `\"${name}\" has missing or invalid stateMutability; mint requires payable or nonpayable`);\n }\n bad(`${prefix}.functionName`, `\"${name}\" is a view or pure function and cannot mint`);\n }\n if (roleFits.length > 1) {\n bad(`${prefix}.functionName`, `\"${String(c.functionName)}\" is ambiguous: ${roleFits.map((x) => signature(x.f)).join(' and ')} both accept these arguments`);\n }\n return roleFits[0]!.f;\n}\n\ntype OutputRef = { tuple: boolean; index: number; type: string };\n\n/** Resolves which return value a read field uses. Throws INVALID_CONFIG naming the bad field. */\nfunction resolveOutput(fn: AbiFunction, output: unknown, path: string): OutputRef {\n const outs = fn.outputs;\n if (outs.length === 0) bad(`${path}.functionName`, `\"${fn.name}\" returns nothing`);\n const first = outs[0]!;\n const tuple = outs.length === 1 && first.type === 'tuple';\n const members = tuple ? ((first as { components?: readonly { name?: string; type: string }[] }).components ?? []) : outs;\n const memberNames = members.map((m, i) => m.name || String(i)).join(', ');\n let index: number;\n if (output === undefined) {\n if (members.length !== 1) bad(`${path}.output`, `is required: \"${fn.name}\" returns several values (${memberNames})`);\n index = 0;\n } else if (typeof output === 'number') {\n if (!Number.isInteger(output) || output < 0 || output >= members.length) {\n bad(`${path}.output`, `${output} is not in the abi outputs of \"${fn.name}\" (${memberNames})`);\n }\n index = output;\n } else if (typeof output === 'string') {\n index = members.findIndex((m) => m.name === output);\n if (index < 0) bad(`${path}.output`, `\"${output}\" is not in the abi outputs of \"${fn.name}\" (${memberNames})`);\n } else {\n return bad(`${path}.output`, 'must be an index or a name');\n }\n const type = members[index]!.type;\n if (!NUMERIC_TYPE.test(type)) {\n const reason = `returns ${type}, not a number`;\n if (output === undefined) bad(`${path}.functionName`, `\"${fn.name}\" ${reason}`);\n bad(`${path}.output`, `selects a value that is ${type}, not a number`);\n }\n return { tuple, index, type };\n}\n\nfunction validateField(\n abi: Abi,\n name: string,\n src: unknown,\n allowed: readonly Placeholder[],\n isPrice: boolean,\n): void {\n if (typeof src !== 'object' || src === null) bad(name, 'is required and must be { value } or { read }');\n const s = src as { value?: unknown; unit?: unknown; read?: unknown };\n const hasRead = Object.hasOwn(s, 'read');\n const hasValue = Object.hasOwn(s, 'value');\n if (hasRead && hasValue) bad(name, 'must be exactly one of { value } or { read }, not both');\n if (hasRead) {\n if (typeof s.read !== 'object' || s.read === null) bad(`${name}.read`, 'must be { functionName, args?, output? }');\n if (s.unit !== undefined) bad(name, 'unit is only valid with a string value');\n const fn = validateCall(abi, s.read, `${name}.read`, 'read', allowed);\n resolveOutput(fn, (s.read as { output?: unknown }).output, `${name}.read`);\n } else if (typeof s.value === 'bigint') {\n if (s.unit !== undefined) bad(name, 'unit is only valid with a string value');\n if (s.value < 0n) bad(`${name}.value`, 'must not be negative');\n } else if (typeof s.value === 'string') {\n if (s.unit !== 'display') bad(name, 'string values need unit: \"display\"');\n if (!isPrice) bad(name, 'display units are only valid for price; use a bigint in base units');\n if (!DISPLAY_AMOUNT.test(s.value)) bad(`${name}.value`, `\"${s.value}\" is not a decimal number like \"0.01\"`);\n } else {\n bad(name, 'must be { value: bigint }, { value: string, unit: \"display\" } (price only) or { read: {...} }');\n }\n}\n\nfunction hasNonzeroStaticPrice(price: unknown): boolean {\n if (typeof price !== 'object' || price === null || !Object.hasOwn(price, 'value')) return false;\n const v = (price as { value: unknown }).value;\n return typeof v === 'bigint' ? v > 0n : typeof v === 'string' && /[1-9]/.test(v);\n}\n\n/**\n * Validates an agent-written config before any network call. Throws MintError('INVALID_CONFIG', msg,\n * { field }) naming the exact bad field: unknown mint.functionName / read functionName, unknown\n * placeholder (field \"mint.args[i]\"), arg count mismatch, missing price, read output index/name not in the\n * ABI outputs or not a numeric type, display unit outside price, bad addresses.\n */\nexport function validateContractConfig(config: ContractConfig): void {\n const c = config as Partial<ContractConfig> | null | undefined;\n if (!c || typeof c !== 'object') bad('config', 'must be an object');\n if (c.type !== 'contract') bad('type', 'must be \"contract\"');\n if (!Number.isSafeInteger(c.chainId) || (c.chainId as number) <= 0) bad('chainId', 'must be a positive safe integer');\n if (!isHexAddress(c.address)) bad('address', `\"${String(c.address)}\" is not a valid address`);\n if (c.tokenContract !== undefined && !isHexAddress(c.tokenContract)) {\n bad('tokenContract', `\"${String(c.tokenContract)}\" is not a valid address`);\n }\n if (!Array.isArray(c.abi)) bad('abi', 'must be an abi array');\n const abi = c.abi;\n\n const mintFn = validateCall(abi, c.mint, 'mint', 'mint', PLACEHOLDERS);\n\n if (c.price === undefined || c.price === null) bad('price', 'is required');\n validateField(abi, 'price', c.price, NO_PLACEHOLDERS, true);\n const cur = (c.price as { currency?: unknown }).currency;\n if (cur !== 'native' && !(typeof cur === 'object' && cur !== null && isHexAddress((cur as { address?: unknown }).address))) {\n bad('price.currency', 'must be \"native\" or { address } with a valid ERC20 address');\n }\n if (cur === 'native' && mintFn.stateMutability === 'nonpayable' && hasNonzeroStaticPrice(c.price)) {\n bad('price', `is a native price, but \"${mintFn.name}\" is nonpayable and cannot take value`);\n }\n const per = (c.price as { per?: unknown }).per;\n if (per !== undefined && per !== 'unit' && per !== 'total') bad('price.per', 'must be \"unit\" or \"total\"');\n\n // Drop-level reads happen before a wallet is connected, so wallet placeholders do not exist there.\n validateOptional(abi, 'totalMinted', c.totalMinted, NO_PLACEHOLDERS);\n validateOptional(abi, 'maxSupply', c.maxSupply, NO_PLACEHOLDERS);\n validateOptional(abi, 'startTime', c.startTime, NO_PLACEHOLDERS);\n validateOptional(abi, 'endTime', c.endTime, NO_PLACEHOLDERS);\n validateOptional(abi, 'walletMinted', c.walletMinted, PLACEHOLDERS);\n validateOptional(abi, 'walletMax', c.walletMax, PLACEHOLDERS);\n}\n\nfunction validateOptional(abi: Abi, name: string, src: unknown, allowed: readonly Placeholder[]): void {\n if (src !== undefined) validateField(abi, name, src, allowed, false);\n}\n\n// ---------- templating ----------\n\ntype Vars = { recipient?: Address; payer?: Address; quantity?: bigint; unitPrice?: bigint; totalPrice?: bigint };\n\nfunction varFor(token: string, vars: Vars): bigint | Address {\n const key = token.slice(1, -1) as keyof Vars;\n const v = vars[key];\n if (v === undefined) throw new MintError('INVALID_CONFIG', `placeholder ${token} has no value here`);\n return typeof v === 'string' ? getAddress(v) : v;\n}\n\n/**\n * Fills a template for ABI input `p` (already checked by typeIssue). A string that is exactly one placeholder\n * becomes the typed value; embedded placeholders are stringified; integer literals become bigint.\n */\nfunction fill(t: ArgTemplate, p: AbiParameter, vars: Vars): unknown {\n const arr = ARRAY_TYPE.exec(p.type);\n if (arr && Array.isArray(t)) return t.map((x) => fill(x, elementParam(p, arr[1]!), vars));\n if (p.type === 'tuple' && Array.isArray(t)) return t.map((x, i) => fill(x, componentsOf(p)[i]!, vars));\n if (typeof t === 'number') return BigInt(t);\n if (typeof t !== 'string') return t;\n if (p.type === 'string') return t.replace(PLACEHOLDER_TOKEN, (token) => String(varFor(token, vars)));\n if (isExactPlaceholder(t)) return varFor(t, vars);\n if (intBounds(p.type)) return BigInt(t);\n return t;\n}\n\nconst fillArgs = (args: readonly ArgTemplate[] | undefined, inputs: readonly AbiParameter[], vars: Vars): unknown[] =>\n (args ?? []).map((a, i) => fill(a, inputs[i]!, vars));\n\n/** {unitPrice} is the configured price as reported in DropInfo; {totalPrice} is what the mint costs. */\nconst totalFor = (per: 'unit' | 'total', price: bigint, quantity: bigint): bigint => (per === 'total' ? price : price * quantity);\n\n// ---------- chain reads ----------\n\nfunction shortMessage(e: unknown): string {\n return e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);\n}\n\nconst abortError = (): Error => new DOMException('The operation was aborted', 'AbortError');\nconst isAbortError = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/** Rejects a pre-aborted signal before any work starts. */\nfunction throwIfAborted(ctx: AdapterContext): void {\n if (ctx.signal?.aborted) throw abortError();\n}\n\n/** Runs one client operation; an abort rejects promptly even if the transport never settles. */\nfunction io<T>(ctx: AdapterContext, start: () => Promise<T>): Promise<T> {\n const signal = ctx.signal;\n if (!signal) return start();\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener('abort', onAbort, { once: true });\n start().then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n });\n}\n\nfunction rpcError(e: unknown): never {\n if (isMintError(e) || isAbortError(e)) throw e;\n throw new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\n/** A contract that reverts or returns no data is a config problem; anything else is the RPC's. */\nfunction classifyReadError(e: unknown, field: string): never {\n if (isMintError(e) || isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {\n throw new MintError('INVALID_CONFIG', `${field} read reverted or returned no data: ${e.shortMessage}`, { field, cause: e });\n }\n return rpcError(e);\n}\n\nasync function readCall(\n ctx: AdapterContext,\n params: { address: Address; abi: Abi; functionName: string; args?: readonly unknown[] },\n field: string,\n): Promise<unknown> {\n try {\n return await io(ctx, () => ctx.client.readContract(params as never));\n } catch (e) {\n return classifyReadError(e, field);\n }\n}\n\nfunction toBigInt(v: unknown, field: string): bigint {\n const n = typeof v === 'number' ? BigInt(v) : v;\n if (typeof n !== 'bigint') throw new MintError('INVALID_CONFIG', `${field} did not return a number`, { field });\n if (n < 0n) throw new MintError('INVALID_CONFIG', `${field} returned a negative number`, { field });\n return n;\n}\n\nasync function readField(ctx: AdapterContext, config: ContractConfig, name: string, src: FieldSource, vars: Vars): Promise<bigint> {\n if (Object.hasOwn(src, 'read') && (src as { read?: unknown }).read != null) {\n const read = (src as Extract<FieldSource, { read: unknown }>).read;\n const path = `${name}.read`;\n const fn = validateCall(config.abi, read, path, 'read', PLACEHOLDERS);\n const args = fillArgs(read.args, fn.inputs, vars);\n const ref = resolveOutput(fn, read.output, path);\n const res = await readCall(ctx, { address: config.address, abi: [fn], functionName: fn.name, args }, `${path}.functionName`);\n const members = fn.outputs.length === 1 && !ref.tuple ? [res] : ref.tuple ? tupleMembers(res, fn, ref.index) : (res as readonly unknown[]);\n return toBigInt(members[ref.index], path);\n }\n const value = (src as { value: unknown }).value;\n if (typeof value === 'bigint') return value;\n throw new MintError('INVALID_CONFIG', `${name} display units are only valid for price`, { field: name });\n}\n\n/** viem returns a struct as an object when every component is named, else as an array. */\nfunction tupleMembers(res: unknown, fn: AbiFunction, index: number): unknown[] {\n if (Array.isArray(res)) return res;\n const comps = (fn.outputs[0] as { components?: readonly { name?: string }[] }).components ?? [];\n const out: unknown[] = [];\n out[index] = (res as Record<string, unknown>)[comps[index]?.name ?? ''];\n return out;\n}\n\nconst optField = (ctx: AdapterContext, config: ContractConfig, name: string, src: FieldSource | undefined, vars: Vars) =>\n src ? readField(ctx, config, name, src, vars) : Promise.resolve(null);\n\nfunction toSeconds(v: bigint | null, field: string): number | null {\n if (v === null || v === 0n) return null;\n if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError('INVALID_CONFIG', `${field} is too large for a unix timestamp`, { field });\n return Number(v);\n}\n\n// ---------- currency and price ----------\n\ntype Meta = { token: Address | null; decimals: number; symbol: string };\n\nfunction nativeMeta(ctx: AdapterContext): { decimals: number; symbol: string } {\n const n = ctx.client.chain?.nativeCurrency;\n return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? 'ETH' };\n}\n\nconst erc20Of = (config: ContractConfig): Address | null =>\n config.price.currency === 'native' ? null : getAddress(config.price.currency.address);\n\nasync function currencyMeta(ctx: AdapterContext, config: ContractConfig): Promise<Meta> {\n const token = erc20Of(config);\n if (!token) return { token, ...nativeMeta(ctx) };\n const field = 'price.currency.address';\n const [symbol, decimals] = await Promise.all([\n readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'symbol' }, field),\n readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'decimals' }, field),\n ]);\n if (typeof symbol !== 'string' || typeof decimals !== 'number') {\n throw new MintError('INVALID_CONFIG', `${field} ${token} is not an ERC20 (symbol/decimals unreadable)`, { field });\n }\n return { token, decimals, symbol };\n}\n\nfunction parseDisplay(value: string, decimals: number): bigint {\n const frac = value.split('.')[1] ?? '';\n if (frac.length > decimals) {\n throw new MintError('INVALID_CONFIG', `price.value \"${value}\" has more than ${decimals} decimal places`, { field: 'price.value' });\n }\n return parseUnits(value, decimals);\n}\n\nasync function resolvePrice(ctx: AdapterContext, config: ContractConfig, meta?: Meta): Promise<bigint> {\n const price = config.price;\n if (Object.hasOwn(price, 'read')) return readField(ctx, config, 'price', price, {});\n const value = (price as { value: bigint | string }).value;\n if (typeof value === 'bigint') return value;\n return parseDisplay(value, (meta ?? (await currencyMeta(ctx, config))).decimals);\n}\n\nconst money = (value: bigint, decimals: number, symbol: string): Money => ({\n value,\n decimals,\n symbol,\n formatted: formatUnits(value, decimals),\n});\n\n// ---------- simulation ----------\n\nfunction revertData(e: unknown): Hex | null {\n for (let cur: unknown = e, depth = 0; cur && depth < 12; depth++) {\n const d = (cur as { data?: unknown }).data;\n if (typeof d === 'string' && d.startsWith('0x') && d.length >= 10) return d as Hex;\n if (d && typeof d === 'object' && typeof (d as { data?: unknown }).data === 'string') {\n const inner = (d as { data: string }).data;\n if (inner.startsWith('0x') && inner.length >= 10) return inner as Hex;\n }\n cur = (cur as { cause?: unknown }).cause;\n }\n return null;\n}\n\nfunction describeRevert(abi: Abi, data: Hex): string {\n try {\n const d = decodeErrorResult({ abi, data });\n const args = (d.args ?? []) as readonly unknown[];\n if (d.errorName === 'Error') return String(args[0] ?? 'execution reverted');\n const inputs = ((d as { abiItem?: { inputs?: readonly { name?: string }[] } }).abiItem?.inputs ?? []) as readonly { name?: string }[];\n const shown = args.map((a, i) => (inputs[i]?.name ? `${inputs[i]!.name}: ${String(a)}` : String(a)));\n return `${d.errorName}(${shown.join(', ')})`;\n } catch {\n return `reverted with unrecognized error ${data.slice(0, 10)}`;\n }\n}\n\nfunction toSimulationError(e: unknown, abi: Abi): MintError {\n if (isMintError(e)) return e;\n if (isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {\n return new MintError('INSUFFICIENT_FUNDS', `Payer cannot cover the mint: ${e.shortMessage}`, { cause: e });\n }\n const data = revertData(e);\n if (data) return new MintError('SIMULATION_REVERTED', describeRevert(abi, data), { cause: e });\n const isRevert = e instanceof BaseError && e.walk((x) => (x as { code?: number }).code === 3 || /execution reverted/i.test((x as Error).message));\n if (isRevert) return new MintError('SIMULATION_REVERTED', shortMessage(e), { cause: e });\n return new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\ntype MintCall = { payer: Address; to: Address; data: Hex; value: bigint };\n\nasync function simulateMint(ctx: AdapterContext, config: ContractConfig, call: MintCall): Promise<void> {\n try {\n await io(ctx, () => ctx.client.call({ account: call.payer, to: call.to, data: call.data, value: call.value }));\n } catch (e) {\n throw toSimulationError(e, config.abi);\n }\n}\n\nfunction encodeMint(config: ContractConfig, vars: Vars): Hex {\n const fn = validateCall(config.abi, config.mint, 'mint', 'mint', PLACEHOLDERS);\n try {\n return encodeFunctionData({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) as never } as never);\n } catch (e) {\n if (isMintError(e)) throw e;\n throw new MintError('INVALID_CONFIG', `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage(e)}`, { field: 'mint.args', cause: e });\n }\n}\n\n/** Native value goes only to a payable mint; a nonpayable mint with a nonzero native total would always revert. */\nfunction mintValue(config: ContractConfig, token: Address | null, totalPrice: bigint): bigint {\n if (token) return 0n;\n const fn = validateCall(config.abi, config.mint, 'mint', 'mint', PLACEHOLDERS);\n if (fn.stateMutability === 'nonpayable') {\n if (totalPrice > 0n) bad('price', `is ${totalPrice} wei in native currency, but \"${fn.name}\" is nonpayable and cannot take value`);\n return 0n;\n }\n return totalPrice;\n}\n\n// ---------- adapter ----------\n\nasync function readDrop(ctx: AdapterContext, config: ContractConfig): Promise<DropInfo> {\n validateContractConfig(config);\n throwIfAborted(ctx);\n const meta = await currencyMeta(ctx, config);\n const none: Vars = {};\n const [price, totalMinted, maxSupply, start, end] = await Promise.all([\n resolvePrice(ctx, config, meta),\n optField(ctx, config, 'totalMinted', config.totalMinted, none),\n optField(ctx, config, 'maxSupply', config.maxSupply, none),\n optField(ctx, config, 'startTime', config.startTime, none),\n optField(ctx, config, 'endTime', config.endTime, none),\n ]);\n mintValue(config, meta.token, price);\n const native = nativeMeta(ctx);\n return {\n chainId: config.chainId,\n unitPrice: money(price, meta.decimals, meta.symbol),\n unitFee: money(0n, native.decimals, native.symbol),\n totalMinted: totalMinted ?? 0n,\n maxSupply: maxSupply === null || maxSupply === 0n ? null : maxSupply,\n startTime: toSeconds(start, 'startTime'),\n endTime: toSeconds(end, 'endTime'),\n isAllowlist: false,\n paymentToken: meta.token,\n pricePer: config.price.per ?? 'unit',\n };\n}\n\nasync function readWallet(\n ctx: AdapterContext,\n config: ContractConfig,\n { payer, recipient, quantity, drop }: { payer: Address; recipient: Address; quantity: number; drop: DropInfo },\n): Promise<WalletRead> {\n validateContractConfig(config);\n throwIfAborted(ctx);\n const qty = BigInt(quantity);\n const unitPrice = drop.unitPrice.value;\n const totalPrice = totalFor(drop.pricePer, unitPrice, qty);\n const vars: Vars = { payer, recipient, quantity: qty, unitPrice, totalPrice };\n\n const [walletMinted, rawMax, native, erc20] = await Promise.all([\n optField(ctx, config, 'walletMinted', config.walletMinted, vars),\n optField(ctx, config, 'walletMax', config.walletMax, vars),\n io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),\n drop.paymentToken\n ? readCall(ctx, { address: drop.paymentToken, abi: ERC20_ABI, functionName: 'balanceOf', args: [payer] }, 'price.currency.address').then((v) =>\n toBigInt(v, 'price.currency.address'),\n )\n : Promise.resolve(null),\n ]);\n // walletMax 0 = no limit\n const walletMax = rawMax === null || rawMax === 0n ? null : rawMax;\n const remaining = walletMax === null ? null : walletMax > (walletMinted ?? 0n) ? walletMax - (walletMinted ?? 0n) : 0n;\n const maxQuantity = remaining === null ? null : Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));\n\n let ineligible: WalletRead['ineligible'] = null;\n let gas = FALLBACK_MINT_GAS;\n if (remaining === 0n) {\n ineligible = { reason: 'WALLET_LIMIT', message: `This wallet has already minted its limit of ${walletMax}.` };\n } else if (drop.paymentToken === null && native >= totalPrice) {\n // An ERC20-priced mint cannot be simulated before its approve, and a payer who cannot cover the value\n // is reported as insufficient funds by the machine, so only simulate when the call can run.\n const call: MintCall = { payer, to: getAddress(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };\n try {\n await simulateMint(ctx, config, call);\n gas = await io(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {\n throw toSimulationError(e, config.abi);\n });\n } catch (e) {\n if (!isMintError(e) || e.reason !== 'SIMULATION_REVERTED') throw e;\n ineligible = { reason: 'SIMULATION_REVERTED', message: e.message };\n }\n }\n if (drop.paymentToken) gas += APPROVE_GAS;\n const gasPrice = await io(ctx, () => ctx.client.getGasPrice()).catch(rpcError);\n\n return {\n walletMinted,\n walletMax,\n allowlistRemaining: null,\n maxQuantity,\n ineligible,\n balances: { native, erc20 },\n gasReserve: gas * gasPrice,\n };\n}\n\nasync function buildPlan(ctx: AdapterContext, attempt: Attempt & { config: ContractConfig }, wallet: WalletRead): Promise<MintPlan> {\n void wallet;\n const { config, payer, recipient } = attempt;\n validateContractConfig(config);\n throwIfAborted(ctx);\n const qty = BigInt(attempt.quantity);\n const unitPrice = await resolvePrice(ctx, config);\n const totalPrice = totalFor(config.price.per ?? 'unit', unitPrice, qty);\n const to = getAddress(config.address);\n const token = erc20Of(config);\n const mint = {\n to,\n data: encodeMint(config, { payer, recipient, quantity: qty, unitPrice, totalPrice }),\n value: mintValue(config, token, totalPrice),\n };\n let approve: MintPlan['approve'] = null;\n if (token) {\n const allowance = toBigInt(\n await readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'allowance', args: [payer, to] }, 'price.currency.address'),\n 'price.currency.address',\n );\n if (allowance < totalPrice) {\n approve = {\n token,\n spender: to,\n amount: totalPrice,\n tx: { to: token, data: encodeFunctionData({ abi: ERC20_ABI, functionName: 'approve', args: [to, totalPrice] }), value: 0n },\n };\n }\n }\n return { approve, mint, tokenContract: getAddress(attempt.tokenContract) };\n}\n\nasync function simulate(ctx: AdapterContext, attempt: Attempt & { config: ContractConfig }, plan: MintPlan): Promise<void> {\n throwIfAborted(ctx);\n await simulateMint(ctx, attempt.config, { payer: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });\n}\n\nexport const contractAdapter: MintAdapter<ContractConfig> = { readDrop, readWallet, buildPlan, simulate };\n","// Pay-with picker: a port of claim-widgets RelayNetworkDropdown (spec R13, D15) without ethers,\n// Zustand or Tailwind. Unstyled, like MintButton. Quotes every pay chain only while mounted.\nimport { createElement, useCallback, useEffect, useState, type ReactNode } from 'react';\n\nimport { getConnectedBalance, switchToChain } from '../core/wallet';\nimport { getQuote } from '../core/relay/client';\nimport { chainName, getChain } from '../core/rpc';\nimport { formatUnits } from 'viem';\nimport type { Money } from '../core/types';\nimport type { MintState } from './useMint';\n\n/** claim-widgets refreshes every 20s (RelayNetworkDropdown.tsx:114-119). */\nexport const PICKER_REFRESH_MS = 20_000;\n/** Quantity steps rebuild the tx; wait for them to settle before quoting every chain (N2). */\nexport const PICKER_DEBOUNCE_MS = 500;\nconst RELAY_REASONS = new Set(['BRIDGE_UNAVAILABLE', 'BRIDGE_TIMEOUT', 'BRIDGE_REFUNDED', 'BRIDGE_FAILED']);\n\nexport type PayOption = {\n chainId: number;\n name: string;\n /** The wallet's current chain. */\n selected: boolean;\n /** The drop's own chain: no bridge, no fee (\"None\"). */\n isDropChain: boolean;\n /** Relay's fee to pay from this chain; null on the drop chain or before the quote. */\n relayFee: Money | null;\n /** Total leaving the wallet on this chain; null on the drop chain or before the quote. */\n amountIn: Money | null;\n /** Balance through the wallet, for the selected chain only (spec D14). */\n balance: Money | null;\n loading: boolean;\n /** Pick this chain: asks the wallet to switch to it. */\n select: () => void;\n};\n\nexport type PayWithPickerProps = {\n /** From useMint, or from MintButton's function child. */\n mint: MintState;\n className?: string;\n /** Replaces the default row markup. */\n renderRow?: (row: PayOption) => ReactNode;\n /** Called after a pick, with the chain id, once the wallet switched (or failed to). */\n onSelect?: (chainId: number, error: unknown) => void;\n};\n\ntype Quoted = { relayFee: Money; amountIn: Money } | 'failed';\n\nconst money = (value: bigint, chainId: number): Money => {\n const c = getChain(chainId).nativeCurrency;\n return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits(value, c.decimals) };\n};\n\n/** Rows for every pay chain; a chain whose quote failed is hidden, the drop chain never is. */\nexport function usePayOptions(mint: MintState, onSelect?: (chainId: number, error: unknown) => void): PayOption[] {\n const info = mint.crossChain;\n const current = mint.wallet?.chainId ?? null;\n const [quotes, setQuotes] = useState<Record<number, Quoted | undefined>>({});\n const [balance, setBalance] = useState<{ chainId: number; value: bigint } | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n const t = setInterval(() => setTick((n) => n + 1), PICKER_REFRESH_MS);\n return () => clearInterval(t);\n }, []);\n\n const key = info ? `${info.tx.to}:${info.tx.value}:${info.tx.data}:${info.payer}:${info.recipient}:${info.payChains.join()}` : '';\n // A new tx (quantity, recipient, payer) invalidates every fee shown: back to loading.\n const [quotedKey, setQuotedKey] = useState(key);\n if (quotedKey !== key) {\n setQuotedKey(key);\n setQuotes({});\n }\n useEffect(() => {\n if (!info) return;\n const controller = new AbortController();\n const timer = setTimeout(() => {\n for (const chainId of info.payChains) {\n if (chainId === info.dropChainId) continue;\n getQuote({ testnet: info.testnet, payer: info.payer, recipient: info.recipient, originChainId: chainId, destinationChainId: info.dropChainId, tx: info.tx, signal: controller.signal }).then(\n (q) => {\n if (!controller.signal.aborted) setQuotes((prev) => ({ ...prev, [chainId]: { relayFee: q.relayFee, amountIn: q.amountIn } }));\n },\n (e: unknown) => {\n // Rate limiting says nothing about the chain: keep the row and try again on the next tick.\n if (controller.signal.aborted || (e as { reason?: string } | null)?.reason === 'RATE_LIMITED') return;\n setQuotes((prev) => ({ ...prev, [chainId]: 'failed' }));\n },\n );\n }\n }, PICKER_DEBOUNCE_MS);\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `key` captures every input of `info`.\n }, [key, tick]);\n\n useEffect(() => {\n if (!info || current === null || !mint.wallet) return;\n let alive = true;\n getConnectedBalance(mint.wallet.address, current).then(\n (value) => {\n if (alive) setBalance({ chainId: current, value });\n },\n () => {\n if (alive) setBalance(null);\n },\n );\n return () => {\n alive = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- re-read on chain/account change and on refresh.\n }, [current, mint.wallet?.address, key, tick]);\n\n const select = useCallback(\n (chainId: number) => {\n switchToChain(chainId).then(\n () => onSelect?.(chainId, null),\n (e: unknown) => onSelect?.(chainId, e),\n );\n },\n [onSelect],\n );\n\n if (!info) return [];\n return info.payChains\n .filter((chainId) => chainId === info.dropChainId || quotes[chainId] !== 'failed')\n .map((chainId): PayOption => {\n const q = quotes[chainId];\n const isDropChain = chainId === info.dropChainId;\n const quoted = q && q !== 'failed' ? q : null;\n return {\n chainId,\n name: chainName(chainId),\n selected: chainId === current,\n isDropChain,\n relayFee: isDropChain ? null : (quoted?.relayFee ?? null),\n amountIn: isDropChain ? null : (quoted?.amountIn ?? null),\n balance: balance && balance.chainId === chainId && chainId === current ? money(balance.value, chainId) : null,\n loading: !isDropChain && q === undefined,\n select: () => select(chainId),\n };\n });\n}\n\nfunction defaultRow(row: PayOption): ReactNode {\n const fee = row.isDropChain ? 'None' : row.loading ? '…' : row.relayFee ? `${row.relayFee.formatted} ${row.relayFee.symbol}` : '--';\n const balance = row.balance ? ` · ${row.balance.formatted} ${row.balance.symbol}` : '';\n return `${row.name}${balance} · fee ${fee}`;\n}\n\n/**\n * One button per pay chain, showing Relay's fee (the drop chain shows \"None\") and, for the wallet's\n * current chain only, its balance. Picking a row switches the wallet; the button re-reads the route.\n * Renders nothing when the drop cannot be minted cross-chain for this wallet.\n */\nexport function PayWithPicker(props: PayWithPickerProps): ReactNode {\n const rows = usePayOptions(props.mint, props.onSelect);\n if (rows.length === 0) return null;\n const open = props.mint.state === 'insufficient-funds' || (props.mint.reason !== null && RELAY_REASONS.has(props.mint.reason));\n return createElement(\n 'div',\n { className: props.className, role: 'group', 'aria-label': 'Pay with', 'data-open': open ? 'true' : 'false' },\n rows.map((row) =>\n createElement(\n 'button',\n {\n key: row.chainId,\n type: 'button',\n 'data-chain': String(row.chainId),\n 'data-selected': row.selected ? 'true' : 'false',\n 'data-loading': row.loading ? 'true' : 'false',\n 'aria-pressed': row.selected,\n // Only an idle button may change chains: a click in progress already chose its pay chain.\n disabled: row.selected || !(props.mint.canAct || props.mint.state === 'insufficient-funds' || props.mint.state === 'loading'),\n onClick: row.select,\n },\n props.renderRow ? props.renderRow(row) : defaultRow(row),\n ),\n ),\n );\n}\n","// Story s7: the default rendering surface. Spec #ui-ux-flow (state table) + #package-api.\nimport { createElement, type ReactNode } from 'react';\n\nimport { isAsyncState, useMint, type MintState, type UseMintOptions } from './useMint';\nimport type { MintStateName } from '../core/machine';\nimport { chainName } from '../core/rpc';\n\nexport type MintButtonProps = UseMintOptions & {\n className?: string;\n /** Per-state text override, e.g. { live: 'Collect', 'sold-out': 'Gone' }. */\n labels?: Partial<Record<MintStateName, string>>;\n /** A function child replaces the default markup entirely; m.mint still drives the flow. */\n children?: ReactNode | ((m: MintState) => ReactNode);\n};\n\n/** Spec #ui-ux-flow \"Default label\" column. No CSS ships. */\nexport function defaultLabel(m: MintState): string {\n const symbol = m.total?.erc20?.symbol ?? m.total?.native.symbol ?? m.drop?.unitPrice.symbol ?? '';\n switch (m.state) {\n case 'loading':\n return 'Loading…';\n case 'unsupported':\n return 'Unavailable';\n case 'upcoming': {\n const t = m.drop?.startTime;\n return `Starts ${t ? new Date(t * 1000).toLocaleString() : 'soon'}`;\n }\n case 'ended':\n return 'Ended';\n case 'sold-out':\n return 'Sold out';\n case 'disconnected':\n return 'Connect wallet';\n case 'connecting':\n return 'Connecting…';\n case 'live': {\n const route = m.route;\n if (route?.kind === 'relay') {\n return `Mint · ${route.amountIn.formatted} ${route.amountIn.symbol} on ${chainName(route.originChainId)}`;\n }\n const total = m.total;\n if (!total) return 'Mint';\n const legs = total.erc20 ? [total.erc20, ...(total.native.value > 0n ? [total.native] : [])] : [total.native];\n return `Mint · ${legs.map(leg => `${leg.formatted} ${leg.symbol}`).join(' + ')}`.trimEnd();\n }\n case 'ineligible':\n return 'Not eligible';\n case 'insufficient-funds': {\n const erc20 = m.total?.erc20;\n // Core supplies the failing currency in its funds message; native has priority when both are short.\n const shortage = erc20 && m.message === `Not enough ${erc20.symbol} to cover the mint.`\n ? erc20.symbol : m.total?.native.symbol ?? symbol;\n return `Insufficient ${shortage}`.trimEnd();\n }\n case 'switching-chain':\n return 'Switching network…';\n case 'approving':\n return `Approving ${symbol}…`.replace(' …', '…');\n case 'minting':\n return 'Confirm in wallet…';\n case 'confirming':\n return 'Minting…';\n case 'success':\n return 'Minted';\n case 'error':\n return 'Try again';\n }\n}\n\nexport function MintButton(props: MintButtonProps): ReactNode {\n const { className, labels, children, ...options } = props;\n const m = useMint(options);\n\n if (typeof children === 'function') return (children as (m: MintState) => ReactNode)(m);\n\n const label = labels?.[m.state] ?? defaultLabel(m);\n return createElement(\n 'button',\n {\n type: 'button',\n className,\n 'data-state': m.state,\n ...(m.reason ? { 'data-reason': m.reason } : {}),\n ...(isAsyncState(m.state) ? { 'aria-busy': 'true' } : {}),\n disabled: !m.canAct,\n onClick: m.mint,\n },\n children ?? label,\n );\n}\n","export const VERSION = '0.3.0';\n\n// React surface (story s7).\nexport { MintButton, PayWithPicker, useMint, usePayOptions } from './react';\nexport type { CrossChainInfo, MintButtonProps, MintState, PayOption, PayWithPickerProps, UseMintOptions } from './react';\n\n// Core types an agent writes or reads.\nexport type {\n Address,\n ArgTemplate,\n Attempt,\n ContractConfig,\n CrossChainOption,\n DropInfo,\n Erc20Ref,\n FieldSource,\n Hex,\n ManifoldClaimConfig,\n MintConfig,\n MintEvent,\n MintEventType,\n MintResult,\n MintedToken,\n Money,\n Route,\n RpcUrls,\n WalletInfo,\n} from './core/types';\nexport { MINT_STATES, CLICKABLE_STATES, MAX_QUANTITY } from './core/machine';\nexport type { MintStateName } from './core/machine';\nexport { MintError, isMintError, REASON_CODES } from './core/errors';\nexport type { ErrorReason, IneligibleReason, ReasonCode, UnsupportedReason } from './core/errors';\n"]}
1
+ {"version":3,"sources":["/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/index.cjs","../src/react/useMint.ts","../src/core/relay/client.ts","../src/core/relay/status.ts","../src/core/adapters/tl-stacks/index.ts","../src/core/adapters/tl-stacks/abis.ts","../src/core/adapters/tl-stacks/allowlist.ts","../src/core/relay/outstanding.ts","../src/core/adapters/contract/index.ts","../src/react/PayWithPicker.ts","../src/react/MintButton.ts","../src/index.ts"],"names":["details","formatUnits","getAddress","zeroAddress","keccak256","BaseError","InsufficientFundsError","decodeErrorResult","encodeFunctionData","isAddress","bad","isAbortError","wallet","hash","receipt","readDrop","useState","useCallback","createElement"],"mappings":"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","file":"/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/index.cjs","sourcesContent":[null,"// Story s7: the React layer. One useReducer over core/machine per instance, no globals.\n// Spec: docs/specs/mint-button.html #package-api, #ui-ux-flow, #state-management, #edge-cases.\nimport { useCallback, useEffect, useReducer, useRef, useState } from 'react';\nimport { BaseError, ExecutionRevertedError, publicActions, type PublicClient, type WalletClient } from 'viem';\n\nimport { chainName, createReadClient, getChain } from '../core/rpc';\nimport { getAccount, getConnectedBalance, getWalletClient, loadConnectWidget, openConnectModal, subscribeAccount, switchToChain, waitForDeposit } from '../core/wallet';\nimport { getQuote, isRelayTestnet, payChainsFor, type RelayQuote } from '../core/relay/client';\nimport { pollUntilTerminal } from '../core/relay/status';\nimport { tlStacksAdapter } from '../core/adapters/tl-stacks';\nimport { claimDeposit, clearOutstanding, getOutstanding, outstandingKey, releaseDeposit, setOutstanding, type Outstanding } from '../core/relay/outstanding';\nimport { manifoldClaimAdapter } from '../core/adapters/manifold-claim';\nimport { contractAdapter } from '../core/adapters/contract';\nimport { parseMintReceipt } from '../core/adapters/receipt';\nimport { recordClaimMint } from '../core/mint-record';\nimport { MintError, UNSUPPORTED_REASONS, isMintError, toReason } from '../core/errors';\nimport type { ErrorReason, ReasonCode, UnsupportedReason } from '../core/errors';\nimport { friendlyErrorMessage } from '../core/friendly-errors';\nimport {\n IN_FLIGHT_STATES,\n canAct as coreCanAct,\n initialState,\n reducer,\n selectMaxQuantity,\n selectTotal,\n} from '../core/machine';\nimport type { Account, MachineState, MintStateName, PayShort } from '../core/machine';\nimport type { AdapterContext, MintAdapter, MintPlan, ReceiptLog, TxRequest, WalletRead } from '../core/adapters/types';\nimport type { Address, Attempt, CrossChainOption, DropInfo, Hex, MintConfig, MintEvent, MintEventType, MintResult, Money, Route, RpcUrls, WalletInfo } from '../core/types';\n\n/** Options for useMint and the props MintButton forwards to it (spec #package-api). */\nexport type UseMintOptions = {\n config: MintConfig;\n /** chainId -> JSON-RPC URL (agent's domain-restricted key). */\n rpcUrls?: RpcUrls;\n /** Defaults to the connected wallet. */\n recipient?: Address;\n /** Controlled when onQuantityChange is also supplied. */\n quantity?: number;\n onQuantityChange?: (quantity: number) => void;\n connect?: { walletConnectProjectId?: string };\n onEvent?: (event: MintEvent) => void;\n /**\n * Relay cross-chain mint for Manifold claims. Default on: a wallet on another supported chain pays\n * there and Relay mints on the drop chain. false turns it off; `chains` narrows the pay chains.\n */\n crossChain?: CrossChainOption;\n /** Force the pay chain. The drop chain means a direct mint. Must be one of the pay chains. */\n payChainId?: number;\n};\n\n/** What PayWithPicker needs to quote other chains (spec #public-api). */\nexport type CrossChainInfo = {\n dropChainId: number;\n /** Chains the collector may pay from, the drop chain included. */\n payChains: number[];\n /** The destination call (plan.mint for the current quantity and recipient). */\n tx: TxRequest;\n payer: Address;\n recipient: Address;\n testnet: boolean;\n};\n\n/** Spec #data-models table 2. */\nexport type MintState = {\n state: MintStateName;\n reason: ReasonCode | null;\n message: string | null;\n drop: DropInfo | null;\n wallet: WalletInfo | null;\n quantity: number;\n setQuantity: (quantity: number) => void;\n maxQuantity: number | null;\n total: { native: Money; erc20: Money | null } | null;\n mint: () => void;\n result: MintResult | null;\n refresh: () => Promise<void>;\n canAct: boolean;\n /** How this wallet pays: direct on the drop chain, or Relay from its current chain. null before a wallet read. */\n route: Route | null;\n /** Inputs for PayWithPicker; null when Relay cannot mint this drop for this wallet. */\n crossChain: CrossChainInfo | null;\n};\n\nconst REFRESH_MS = 30_000;\nconst TICK_MS = 1_000;\n/** After a phase boundary the chain may still report the old phase (it reads block time, the clock reads wall time). */\nconst BOUNDARY_RETRY_MS = 3_000;\nconst BOUNDARY_RETRY_MAX = 10;\nconst ASYNC_STATES: readonly MintStateName[] = ['loading', 'connecting', 'switching-chain', 'approving', 'minting', 'confirming'];\n\nexport function isAsyncState(name: MintStateName): boolean {\n return ASYNC_STATES.includes(name);\n}\n\nconst nowSec = (): number => Math.floor(Date.now() / 1000);\n\n/** Stable identity for a config: a change starts a new read lifetime (new AbortController + cacheScope). */\nfunction configKeyOf(config: MintConfig): string {\n return JSON.stringify(config, (_k, v) => (typeof v === 'bigint' ? { __dropkitBigInt: v.toString() } : v));\n}\n\nfunction adapterFor(config: MintConfig): MintAdapter {\n switch (config.type) {\n case 'manifold-claim':\n return manifoldClaimAdapter as unknown as MintAdapter;\n case 'contract':\n return contractAdapter as unknown as MintAdapter;\n case 'tl-stacks':\n return tlStacksAdapter as unknown as MintAdapter;\n default: {\n const unreachable: never = config;\n return unreachable;\n }\n }\n}\n\n/** The Manifold read bridge only serves the Manifold preset's allowlisted contracts (spec #api-endpoints). */\nconst bridgeEnabled = (config: MintConfig): boolean => config.type === 'manifold-claim';\n\n/** Pay chains for this config and option (drop chain included); empty = no Relay (spec R1, R2). */\nexport function payChainsOf(config: MintConfig, option: CrossChainOption | undefined): number[] {\n if (config.type !== 'manifold-claim' || option === false) return [];\n const all = payChainsFor(config.chainId);\n const narrowed = typeof option === 'object' && option.chains ? all.filter((id) => id === config.chainId || option.chains!.includes(id)) : all;\n return narrowed.length > 1 ? narrowed : [];\n}\n\n/** The chain this wallet would pay on: payChainId when valid, else the wallet's chain (spec R3, R4). */\nfunction routeChainOf(payChains: readonly number[], accountChainId: number, payChainId: number | undefined): number {\n if (payChainId !== undefined && payChains.includes(payChainId)) return payChainId;\n return accountChainId;\n}\n\nconst ROUTE_DEBOUNCE_MS = 500;\n\nconst symbolOf = (chainId: number): string => {\n try {\n return getChain(chainId).nativeCurrency.symbol;\n } catch {\n return 'ETH';\n }\n};\n\nconst shortOf = (chainId: number): PayShort => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });\n\n\n/** Logs come from the creator contract for the preset, else config.tokenContract ?? the mint target. */\nexport function tokenContractOf(config: MintConfig): Address {\n switch (config.type) {\n case 'manifold-claim':\n return config.creatorContract;\n case 'contract':\n return config.tokenContract ?? config.address;\n case 'tl-stacks':\n return config.nftContract;\n default: {\n const unreachable: never = config;\n return unreachable;\n }\n }\n}\n\n/** onEvent payload address: the preset reports the creator contract (never a wallet). */\nexport function eventAddressOf(config: MintConfig): Address {\n switch (config.type) {\n case 'manifold-claim':\n return config.creatorContract;\n case 'contract':\n return config.address;\n case 'tl-stacks':\n return config.nftContract;\n default: {\n const unreachable: never = config;\n return unreachable;\n }\n }\n}\n\nconst isUnsupported = (reason: ReasonCode): reason is UnsupportedReason => (UNSUPPORTED_REASONS as readonly string[]).includes(reason);\n\nfunction errorReasonOf(e: unknown): { reason: ErrorReason; message: string } {\n const { reason, message } = toReason(e);\n const ok: readonly string[] = ['USER_REJECTED', 'TX_REVERTED', 'RATE_LIMITED', 'RPC_ERROR', 'CHAIN_SWITCH_FAILED', 'ALLOWLIST_UNAVAILABLE'];\n return { reason: (ok.includes(reason) ? reason : 'RPC_ERROR') as ErrorReason, message };\n}\n\nfunction explorerUrlFor(chainId: number, hash: Hex): string | null {\n try {\n const url = getChain(chainId).blockExplorers?.default.url;\n return url ? `${url.replace(/\\/$/, '')}/tx/${hash}` : null;\n } catch {\n return null;\n }\n}\n\n/** A read lifetime: one AbortController + one stable cacheScope per hook/config, shared across refreshes. */\ntype Lifetime = {\n key: string;\n controller: AbortController;\n /** Stable object identity for the whole lifetime (adapters key their shared fetches on it). */\n cacheScope: object;\n /** Current read generation; a result from another generation is dropped. */\n gen: number;\n client?: PublicClient;\n rpcUrl?: string;\n};\n\nconst receiptOk = (status: unknown): boolean => status === 'success' || status === 1 || status === 1n || status === '0x1';\n\ntype Receiptish = { status?: unknown; logs?: readonly ReceiptLog[]; transactionHash?: Hex };\n\nexport function useMint(options: UseMintOptions): MintState {\n const { config, recipient, quantity: quantityProp, connect } = options;\n\n const [machine, rawDispatch] = useReducer(reducer, undefined, () => initialState({ quantity: quantityProp }));\n /** Widget verdict is held OUTSIDE the reducer so a later successful drop read cannot overwrite it (AC-20). */\n const [widgetFail, setWidgetFail] = useState<{ reason: ReasonCode; message: string } | null>(null);\n const [widgetRetry, setWidgetRetry] = useState(0);\n\n const configKey = configKeyOf(config);\n\n\n // --- refs: everything the stable callbacks and async flows read.\n const mounted = useRef(false);\n const stateRef = useRef(machine);\n const propsRef = useRef(options);\n const lifeRef = useRef<Lifetime | null>(null);\n const genRef = useRef(0);\n const attemptRef = useRef<Attempt | null>(null);\n const attemptIdRef = useRef(0);\n const accountKeyRef = useRef<string | null>(null);\n const connectingRef = useRef(false);\n const pendingRefreshRef = useRef(false);\n const boundaryRetryRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingQuantityRef = useRef<number | null>(null);\n const widgetFailRef = useRef<{ reason: ReasonCode; message: string } | null>(null);\n const widgetLoadRef = useRef<Promise<void> | null>(null);\n const pushAccountRef = useRef<((a: Account | null) => void) | null>(null);\n /** Outstanding-request keys this mount already resumed or paid itself. */\n const autoResumedRef = useRef(new Set<string>());\n /** Picker inputs, tagged with the walletGen they were built for: never shown for another account/quantity. */\n const [crossChainInfo, setCrossChainInfo] = useState<{ gen: number; info: CrossChainInfo } | null>(null);\n\n // Mirrors of the committed render: the async flows and the stable callbacks read these instead of\n // closing over one render's values. Written on commit, never during render.\n useEffect(() => {\n stateRef.current = machine;\n propsRef.current = options;\n widgetFailRef.current = widgetFail;\n });\n\n const dispatch = useCallback<typeof rawDispatch>((event) => {\n // Nothing may touch React state after unmount (spec #edge-cases) or for a dead lifecycle.\n if (!mounted.current) return;\n stateRef.current = reducer(stateRef.current, event);\n rawDispatch(event);\n }, []);\n\n const emit = useCallback((type: MintEventType, over: Partial<MintEvent> = {}) => {\n const handler = propsRef.current.onEvent;\n if (!handler || !mounted.current) return;\n const cfg = attemptRef.current?.config ?? propsRef.current.config;\n const s = stateRef.current;\n const originChainId = attemptRef.current?.originChainId ?? (s.route?.kind === 'relay' ? s.route.originChainId : (s.short?.chainId ?? cfg.chainId));\n const payload: MintEvent = {\n type,\n state: s.name,\n reason: s.reason,\n chainId: cfg.chainId,\n quantity: s.quantity,\n config: { type: cfg.type, chainId: cfg.chainId, address: eventAddressOf(cfg) },\n route: originChainId === cfg.chainId ? 'direct' : 'relay',\n originChainId,\n ...over,\n };\n // Consumer analytics must not interrupt a transaction or change its outcome.\n try { handler(payload); } catch { /* the host owns its callback errors */ }\n }, []);\n\n // ---------------------------------------------------------------- reads\n\n const clientFor = useCallback((life: Lifetime, cfg: MintConfig): PublicClient => {\n const rpcUrl = propsRef.current.rpcUrls?.[cfg.chainId];\n if (!life.client || life.rpcUrl !== rpcUrl) {\n life.rpcUrl = rpcUrl;\n life.client = createReadClient({ chainId: cfg.chainId, rpcUrls: propsRef.current.rpcUrls, useBridge: bridgeEnabled(cfg) });\n }\n return life.client;\n }, []);\n\n const ctxFor = useCallback(\n (life: Lifetime, cfg: MintConfig): AdapterContext => ({\n client: clientFor(life, cfg),\n cacheScope: life.cacheScope,\n // The lifetime signal: a config change or unmount cancels every adapter call, including shared merkle fetches.\n signal: life.controller.signal,\n }),\n [clientFor],\n );\n\n const readDrop = useCallback(\n async (life: Lifetime, gen: number, cfg: MintConfig): Promise<void> => {\n const adapter = adapterFor(cfg);\n try {\n const drop = await adapter.readDrop(ctxFor(life, cfg), cfg);\n if (life.controller.signal.aborted || life.gen !== gen || lifeRef.current !== life) return;\n dispatch({ type: 'DROP_LOADED', gen, drop, now: Math.max(stateRef.current.now, nowSec()) });\n } catch (e) {\n if (life.controller.signal.aborted || life.gen !== gen || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n if (isUnsupported(reason)) dispatch({ type: 'DROP_UNSUPPORTED', gen, reason, message });\n else dispatch({ type: 'READ_FAILED', gen, ...errorReasonOf(e) });\n }\n },\n [ctxFor, dispatch],\n );\n\n const startLifetime = useCallback(\n (key: string, cfg: MintConfig) => {\n const prev = lifeRef.current;\n lifeRef.current = null;\n prev?.controller.abort();\n const gen = ++genRef.current;\n const life: Lifetime = { key, controller: new AbortController(), cacheScope: {}, gen };\n lifeRef.current = life;\n dispatch({ type: 'LOAD', gen, kind: 'config' });\n void readDrop(life, gen, cfg);\n },\n [dispatch, readDrop],\n );\n\n const refreshNow = useCallback(\n async (kind: 'refresh' | 'config' = 'refresh'): Promise<void> => {\n const life = lifeRef.current;\n if (!life || !mounted.current) return;\n if (connectingRef.current || attemptRef.current || stateRef.current.attempt) {\n pendingRefreshRef.current = true;\n return;\n }\n const gen = ++genRef.current;\n life.gen = gen;\n dispatch({ type: 'LOAD', gen, kind });\n await readDrop(life, gen, propsRef.current.config);\n },\n [dispatch, readDrop],\n );\n\n const refresh = useCallback(() => refreshNow('refresh'), [refreshNow]);\n\n /** True while a click owns the flow: the synchronous claim wins before the reducer has moved. */\n const isBusy = useCallback(\n () => connectingRef.current || attemptRef.current !== null || stateRef.current.attempt !== null || IN_FLIGHT_STATES.includes(stateRef.current.name),\n [],\n );\n\n // ---------------------------------------------------------------- mint\n\n const executeAttempt = useCallback(\n async (attempt: Attempt, walletRead: WalletRead, life: Lifetime, account: Account): Promise<void> => {\n const cfg = attempt.config;\n const adapter = adapterFor(cfg);\n const ctx = ctxFor(life, cfg);\n const client = clientFor(life, cfg);\n\n /** The frozen payer must still be the selected wallet before every prompt. */\n const assertActive = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n };\n const assertPayer = (): void => {\n assertActive();\n const live = getAccount();\n if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {\n throw new MintError('USER_REJECTED', 'The connected wallet changed before the transaction was sent.');\n }\n // The widget's cached chain can lag a successful switch. sendTransaction below\n // verifies the actual provider chain via viem, with the frozen chain bound.\n };\n\n let wallet: WalletClient | null = null;\n const walletFor = (): WalletClient => {\n if (!wallet) wallet = getWalletClient(attempt.chainId);\n return wallet;\n };\n\n const submit = async (tx: TxRequest): Promise<Hex> => {\n assertPayer();\n const w = walletFor() as unknown as {\n sendTransaction: (a: Record<string, unknown>) => Promise<Hex>;\n };\n return w.sendTransaction({ account: attempt.payer, to: tx.to, data: tx.data, value: tx.value, chain: getChain(attempt.chainId) });\n };\n\n const waitReceipt = async (hash: Hex): Promise<Receiptish> => {\n let cancelled = false;\n const params = {\n hash,\n confirmations: 1,\n timeout: 120_000,\n onReplaced: ({ reason }: { reason: string }) => {\n // Only repricing preserves the signed mint intent. Other replacements may succeed without minting.\n if (reason !== 'repriced') cancelled = true;\n },\n };\n let receipt: Receiptish;\n try {\n receipt = await client.waitForTransactionReceipt(params);\n } catch (e) {\n assertActive();\n if (cancelled) throw new MintError('USER_REJECTED', 'The transaction was cancelled or replaced with a different transaction.');\n // WalletClient has no receipt actions until explicitly extended with publicActions.\n if (await walletFor().getChainId() !== attempt.chainId) throw e;\n assertActive();\n receipt = await walletFor().extend(publicActions).waitForTransactionReceipt(params);\n }\n assertActive();\n if (cancelled) throw new MintError('USER_REJECTED', 'The transaction was cancelled or replaced with a different transaction.');\n return receipt;\n };\n\n // 1. wrong chain: switch before anything is signed (AC-4).\n assertPayer();\n if (account.chainId !== attempt.chainId) {\n dispatch({ type: 'SWITCHING_CHAIN', attemptId: attempt.attemptId });\n await switchToChain(attempt.chainId);\n }\n assertPayer();\n\n // 2. plan for the frozen attempt.\n let plan: MintPlan = await adapter.buildPlan(ctx, attempt, walletRead);\n assertPayer();\n\n // 3. ERC20 approval leg, simulated through the read client immediately before the prompt (AC-7).\n if (plan.approve) {\n const a = plan.approve;\n try {\n const simulated = await client.call({ account: attempt.payer, to: a.tx.to, data: a.tx.data, value: a.tx.value });\n if (simulated.data && simulated.data !== '0x' && BigInt(simulated.data) === 0n) {\n throw new MintError('SIMULATION_REVERTED', 'The token refused the approval.');\n }\n } catch (e) {\n if (isMintError(e)) throw e;\n const reverted = e instanceof BaseError ? e.walk(cause => cause instanceof ExecutionRevertedError) : null;\n if (reverted instanceof ExecutionRevertedError) {\n throw new MintError('SIMULATION_REVERTED', reverted.shortMessage);\n }\n throw e;\n }\n assertPayer();\n dispatch({ type: 'APPROVING', attemptId: attempt.attemptId });\n const approveHash = await submit(a.tx);\n assertActive();\n emit('approve-submitted', { state: 'approving', reason: null, txHash: approveHash, quantity: attempt.quantity, chainId: attempt.chainId });\n const approveReceipt = await waitReceipt(approveHash);\n assertActive();\n if (!receiptOk(approveReceipt.status)) {\n throw new MintError('TX_REVERTED', `The approval transaction reverted (${approveHash}).`);\n }\n // Approval does not guarantee the drop is still live or the recipient is still eligible.\n const freshDrop = await adapter.readDrop(ctx, cfg);\n assertActive();\n const freshWallet = await adapter.readWallet(ctx, cfg, {\n payer: attempt.payer,\n recipient: attempt.recipient,\n quantity: attempt.quantity,\n drop: freshDrop,\n });\n assertPayer();\n let checked = reducer(initialState({ quantity: attempt.quantity }), { type: 'ACCOUNT_CHANGED', account });\n checked = reducer(checked, { type: 'DROP_LOADED', gen: checked.readGen, drop: freshDrop, now: nowSec() });\n checked = reducer(checked, { type: 'WALLET_LOADED', gen: checked.walletGen, read: freshWallet });\n if (checked.name !== 'live' || (selectMaxQuantity(checked) ?? 0) < attempt.quantity) {\n throw new MintError(checked.reason ?? 'SIMULATION_REVERTED', checked.message ?? 'The drop is no longer available for this quantity.');\n }\n plan = await adapter.buildPlan(ctx, attempt, freshWallet);\n assertPayer();\n }\n\n // 4. simulate the mint from the payer: a revert never becomes a wallet prompt (AC-13).\n await adapter.simulate(ctx, attempt, plan);\n assertPayer();\n\n // 5. mint prompt.\n dispatch({ type: 'MINT_PROMPTED', attemptId: attempt.attemptId });\n const hash = await submit(plan.mint);\n // Manifold's mint record, as the claim widget sends it once the mint is sent. Fire and forget.\n if (cfg.type === 'manifold-claim') void recordClaimMint({ instanceId: cfg.instanceId, hash });\n assertActive();\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('tx-submitted', { state: 'confirming', reason: null, txHash: hash, quantity: attempt.quantity, chainId: attempt.chainId });\n\n const receipt = await waitReceipt(hash);\n assertActive();\n const confirmedHash = receipt.transactionHash ?? hash;\n if (!receiptOk(receipt.status)) {\n throw new MintError('TX_REVERTED', `The mint transaction reverted (${confirmedHash}).`);\n }\n const tokens = parseMintReceipt(receipt.logs ?? [], {\n tokenContract: plan.tokenContract ?? attempt.tokenContract,\n recipient: attempt.recipient,\n });\n const result: MintResult = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens, requested: attempt.quantity };\n dispatch({ type: 'ATTEMPT_SUCCEEDED', attemptId: attempt.attemptId, result });\n emit('success', { state: 'success', reason: null, txHash: confirmedHash, quantity: attempt.quantity, chainId: attempt.chainId });\n },\n [clientFor, ctxFor, dispatch, emit],\n );\n\n // ---------------------------------------------------------------- relay mint (spec #execute-click)\n\n /** Polls a paid Relay request to a terminal status and settles the attempt (steps 6-8). */\n const finishRelay = useCallback(\n async (attempt: Attempt, out: Outstanding, life: Lifetime): Promise<void> => {\n const cfg = attempt.config;\n const client = clientFor(life, cfg);\n const assertActive = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n };\n const outcome = await pollUntilTerminal({ testnet: out.testnet, requestId: out.requestId, signal: life.controller.signal, assertActive });\n assertActive();\n const where = chainName(out.originChainId);\n if (outcome.kind === 'timeout') {\n // The request stays outstanding: the next click resumes it instead of paying again (R12).\n throw new MintError('BRIDGE_TIMEOUT', 'The bridge is still processing. Your mint may still complete.');\n }\n const key = outstandingKey(attempt.config, attempt.payer);\n const { status } = outcome;\n if (status.status !== 'success') clearOutstanding(key);\n if (status.status === 'refund') {\n throw new MintError('BRIDGE_REFUNDED', `Relay could not complete the mint and is refunding your payment. Relay request ${out.requestId}.`);\n }\n if (status.status === 'failure') {\n const why = status.failReason ? ` (${status.failReason})` : '';\n const noRefund = status.refundFailReason ? ` No automatic refund was sent. Contact support with Relay request ${out.requestId}.` : '';\n throw new MintError('BRIDGE_FAILED', `Relay could not complete the mint paid on ${where}${why}.${noRefund}`);\n }\n // success: the drop-chain receipt is best effort. Once Relay says success, nothing here may produce `error`.\n const fillHash = status.txHashes[0] ?? null;\n // The claim widget records the drop-chain fill hash for a Relay mint. Fire and forget.\n if (fillHash && cfg.type === 'manifold-claim') void recordClaimMint({ instanceId: cfg.instanceId, hash: fillHash });\n let tokens: MintResult['tokens'] = [];\n if (fillHash) {\n try {\n const receipt = (await client.waitForTransactionReceipt({ hash: fillHash, confirmations: 1, timeout: 60_000 })) as Receiptish;\n if (receiptOk(receipt.status)) {\n tokens = parseMintReceipt(receipt.logs ?? [], { tokenContract: attempt.tokenContract, recipient: attempt.recipient });\n }\n } catch {\n /* tokens stay [] */\n }\n assertActive();\n }\n const txHash = fillHash ?? out.originTxHash;\n const result: MintResult = {\n txHash,\n explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),\n tokens,\n requested: attempt.quantity,\n relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId },\n };\n // Cleared only now: an unmount during the receipt read leaves it, so a remount resumes and shows success.\n clearOutstanding(key);\n dispatch({ type: 'ATTEMPT_SUCCEEDED', attemptId: attempt.attemptId, result });\n emit('success', { state: 'success', reason: null, txHash, quantity: attempt.quantity, chainId: cfg.chainId });\n },\n [clientFor, dispatch, emit],\n );\n\n const executeRelayAttempt = useCallback(\n async (attempt: Attempt, walletRead: WalletRead, life: Lifetime, account: Account): Promise<void> => {\n const cfg = attempt.config;\n const adapter = adapterFor(cfg);\n const ctx = ctxFor(life, cfg);\n const testnet = isRelayTestnet(cfg.chainId);\n const origin = attempt.originChainId;\n const assertPayer = (): void => {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {\n throw new DOMException('Mint attempt cancelled', 'AbortError');\n }\n const live = getAccount();\n if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {\n throw new MintError('USER_REJECTED', 'The connected wallet changed before the transaction was sent.');\n }\n };\n\n // 3. only payChainId can put the pay chain somewhere the wallet is not.\n assertPayer();\n if (account.chainId !== origin) {\n dispatch({ type: 'SWITCHING_CHAIN', attemptId: attempt.attemptId });\n await switchToChain(origin);\n }\n assertPayer();\n\n // 4. plan on the drop chain, fresh quote (R6), balance through the wallet (D14). No eth_call simulate (D11).\n const plan = await adapter.buildPlan(ctx, attempt, walletRead);\n if (plan.approve) throw new MintError('BRIDGE_UNAVAILABLE', 'Cross-chain mint supports native-priced drops only.');\n assertPayer();\n const quote: RelayQuote = await getQuote({\n testnet,\n payer: attempt.payer,\n recipient: attempt.recipient,\n originChainId: origin,\n destinationChainId: cfg.chainId,\n tx: plan.mint,\n fresh: true,\n signal: life.controller.signal,\n });\n assertPayer();\n const balance = await getConnectedBalance(attempt.payer, origin);\n if (balance < quote.amountIn.value + quote.originGas.value) {\n const s = shortOf(origin);\n throw new MintError('INSUFFICIENT_FUNDS', `Not enough ${s.symbol} on ${s.name}. Pick another network to pay from.`);\n }\n assertPayer();\n\n // 5. deposit on the origin chain, exactly as Relay specified it.\n // parseQuote guarantees exactly one deposit item on the origin chain.\n const item = quote.items[0]!;\n const key = outstandingKey(cfg, attempt.payer);\n const wallet = getWalletClient(origin);\n assertPayer();\n // Another button for this drop and wallet is prompting or already paid: never a second deposit (R12).\n if (!claimDeposit(key)) {\n throw new MintError('BRIDGE_UNAVAILABLE', 'A cross-chain payment for this drop is already in progress. Try again once it finishes.');\n }\n let out: Outstanding;\n try {\n dispatch({ type: 'MINT_PROMPTED', attemptId: attempt.attemptId });\n const send = wallet as unknown as { sendTransaction: (a: Record<string, unknown>) => Promise<Hex> };\n const originTxHash = await send.sendTransaction({\n account: attempt.payer,\n to: item.to,\n data: item.data,\n value: item.value,\n ...(item.gas !== null && item.gas > 0n ? { gas: item.gas } : {}),\n chain: getChain(origin),\n });\n // Recorded before anything else can throw or unmount: from here on, no second deposit (R12).\n out = { requestId: quote.requestId, testnet, originChainId: origin, originTxHash, attempt };\n setOutstanding(key, out);\n } finally {\n releaseDeposit(key);\n }\n const { originTxHash } = out;\n autoResumedRef.current.add(key);\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('tx-submitted', { state: 'confirming', reason: null, txHash: originTxHash, quantity: attempt.quantity, chainId: cfg.chainId });\n\n // 6. a reverted deposit is final; no receipt in time is not (R9): keep polling Relay.\n const deposit = await waitForDeposit(origin, originTxHash);\n if (deposit === 'reverted') {\n clearOutstanding(key);\n throw new MintError('TX_REVERTED', `The payment transaction reverted (${originTxHash}).`);\n }\n await finishRelay(attempt, out, life);\n },\n [ctxFor, dispatch, emit, finishRelay],\n );\n\n /** Resumes polling a request left outstanding by BRIDGE_TIMEOUT, instead of a new deposit (R12). */\n const resumeOutstanding = useCallback(\n async (out: Outstanding, life: Lifetime): Promise<void> => {\n const attempt: Attempt = Object.freeze({ ...out.attempt, attemptId: ++attemptIdRef.current });\n attemptRef.current = attempt;\n dispatch({ type: 'ATTEMPT_STARTED', attempt, resume: true });\n dispatch({ type: 'TX_SUBMITTED', attemptId: attempt.attemptId });\n emit('attempt', { state: 'confirming', reason: null, quantity: attempt.quantity, chainId: attempt.chainId });\n try {\n await finishRelay(attempt, out, life);\n } catch (e) {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n dispatch({ type: 'ATTEMPT_FAILED', attemptId: attempt.attemptId, reason, message });\n emit('error', { quantity: attempt.quantity, chainId: attempt.chainId });\n } finally {\n attemptRef.current = null;\n }\n },\n [dispatch, emit, finishRelay],\n );\n\n const runMint = useCallback(async (): Promise<void> => {\n if (!mounted.current) return;\n if (attemptRef.current !== null) return; // synchronous guard before any retry or await\n if (widgetFailRef.current) {\n if (isUnsupported(widgetFailRef.current.reason)) return;\n widgetFailRef.current = null;\n setWidgetFail(null);\n setWidgetRetry(n => n + 1);\n void refreshNow('config');\n return;\n }\n\n const s = stateRef.current;\n if (!coreCanAct(s) || s.attempt !== null) return;\n\n // --- connect\n if (s.name === 'disconnected') {\n if (connectingRef.current) return;\n connectingRef.current = true;\n dispatch({ type: 'CONNECT_STARTED' });\n const connectLife = lifeRef.current;\n const active = () => mounted.current && lifeRef.current === connectLife && !connectLife?.controller.signal.aborted;\n try {\n await widgetLoadRef.current;\n if (!active()) return;\n const outcome = await openConnectModal();\n if (!active()) return;\n connectingRef.current = false;\n if (outcome === 'connected') {\n accountKeyRef.current = null;\n pushAccountRef.current?.(getAccount());\n }\n else dispatch({ type: 'CONNECT_DISMISSED' });\n } catch {\n if (active()) dispatch({ type: 'CONNECT_DISMISSED' });\n } finally {\n connectingRef.current = false;\n }\n return;\n }\n\n // --- a paid Relay request for this drop and payer is unresolved: resume it, never pay again (R12).\n // Survives unmount (page memory); a terminal Relay status or a reverted deposit releases it.\n const payer = s.account?.address;\n const out = payer ? getOutstanding(outstandingKey(propsRef.current.config, payer)) : null;\n if (out && lifeRef.current) {\n await resumeOutstanding(out, lifeRef.current);\n return;\n }\n\n // --- reset after a terminal state: a fresh drop + wallet read must land before the button is live again.\n if (s.name === 'success' || s.name === 'error') {\n const life = lifeRef.current;\n const gen = ++genRef.current;\n if (life) life.gen = gen;\n dispatch({ type: 'RESET', gen });\n // Current core RESET retains the old drop. Clear it before the wallet effect can read.\n dispatch({ type: 'LOAD', gen, kind: 'config' });\n if (life) void readDrop(life, gen, propsRef.current.config);\n return;\n }\n\n if (s.name !== 'live') return;\n const account = s.account;\n const walletRead = s.walletRead;\n const life = lifeRef.current;\n if (!account || !walletRead || !life) return;\n\n const cfg = propsRef.current.config;\n const relayRoute = s.route?.kind === 'relay' ? s.route : null;\n const attempt: Attempt = Object.freeze({\n attemptId: ++attemptIdRef.current,\n config: structuredClone(cfg),\n payer: account.address,\n recipient: propsRef.current.recipient ?? account.address,\n quantity: s.quantity,\n chainId: cfg.chainId,\n tokenContract: tokenContractOf(cfg),\n originChainId: relayRoute ? relayRoute.originChainId : cfg.chainId,\n });\n attemptRef.current = attempt;\n dispatch({ type: 'ATTEMPT_STARTED', attempt });\n emit('attempt', { state: 'live', reason: null, quantity: attempt.quantity, chainId: attempt.chainId });\n\n try {\n if (relayRoute) await executeRelayAttempt(attempt, walletRead, life, account);\n else await executeAttempt(attempt, walletRead, life, account);\n } catch (e) {\n if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;\n const { reason, message } = toReason(e);\n dispatch({ type: 'ATTEMPT_FAILED', attemptId: attempt.attemptId, reason, message });\n emit('error', { quantity: attempt.quantity, chainId: attempt.chainId });\n } finally {\n attemptRef.current = null;\n }\n }, [dispatch, emit, readDrop, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);\n\n const mint = useCallback(() => {\n void runMint();\n }, [runMint]);\n\n // ---------------------------------------------------------------- effects (none run on the server)\n\n // Declared first so a StrictMode remount re-arms before the read effects run.\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n if (boundaryRetryRef.current) clearTimeout(boundaryRetryRef.current);\n boundaryRetryRef.current = null;\n lifeRef.current?.controller.abort();\n lifeRef.current = null;\n };\n }, []);\n\n // Account subscription, de-duplicated by address:chainId:recipient so one real change is read once.\n useEffect(() => {\n const push = (a: Account | null): void => {\n const key = a ? `${a.address.toLowerCase()}:${a.chainId}:${(propsRef.current.recipient ?? '').toLowerCase()}` : 'null';\n if (key === accountKeyRef.current) return;\n accountKeyRef.current = key;\n if (attemptRef.current || stateRef.current.attempt) pendingRefreshRef.current = true;\n dispatch({ type: 'ACCOUNT_CHANGED', account: a });\n };\n pushAccountRef.current = push;\n push(getAccount());\n const unsubscribe = subscribeAccount(push);\n return () => {\n unsubscribe();\n };\n }, [dispatch]);\n\n // Recipient change: the same wallet needs a fresh eligibility read for the new recipient.\n const recipientRef = useRef(recipient);\n useEffect(() => {\n if (isBusy() || recipientRef.current === recipient) return;\n recipientRef.current = recipient;\n accountKeyRef.current = null;\n pushAccountRef.current?.(getAccount());\n }, [recipient, machine.attempt, isBusy]);\n\n // Connect widget: the verdict lives outside the reducer and wins over the derived state (AC-20).\n const projectId = connect?.walletConnectProjectId;\n // A wallet on any pay chain must stay connected there, not be forced to the drop chain (spec #connect-widget).\n const widgetChains = [config.chainId, ...payChainsOf(config, options.crossChain).filter((id) => id !== config.chainId)].join(',');\n useEffect(() => {\n let alive = true;\n const loading = loadConnectWidget({ chainIds: widgetChains.split(',').map(Number), ...(projectId ? { walletConnectProjectId: projectId } : {}) });\n widgetLoadRef.current = loading;\n loading.then(\n () => {\n if (alive) setWidgetFail(null);\n },\n (e: unknown) => {\n if (!alive) return;\n const { reason, message } = toReason(e);\n setWidgetFail({ reason, message });\n },\n );\n return () => {\n alive = false;\n };\n }, [widgetChains, projectId, widgetRetry]);\n\n // Read lifetime. No dependency array: a deferred config change is applied on the render after the\n // attempt reaches a terminal state (spec #state-management \"Attempt snapshot\").\n useEffect(() => {\n if (!mounted.current) return;\n if (isBusy()) return; // frozen until terminal\n if (pendingQuantityRef.current !== null) {\n const quantity = pendingQuantityRef.current;\n pendingQuantityRef.current = null;\n dispatch({ type: 'SET_QUANTITY', quantity });\n }\n if (lifeRef.current?.key !== configKey) {\n pendingRefreshRef.current = false;\n startLifetime(configKey, config);\n } else if (pendingRefreshRef.current || lifeRef.current.rpcUrl !== options.rpcUrls?.[config.chainId]) {\n pendingRefreshRef.current = false;\n void refreshNow();\n }\n });\n\n // A paid, unresolved Relay request for this drop and payer (from before an unmount or a reload):\n // pick it straight back up, so the collector sees it confirming instead of a live Mint (R12).\n // Once per key per mount: after that, a timed-out request waits for the collector's click.\n const resumeKey = machine.account ? outstandingKey(config, machine.account.address) : null;\n useEffect(() => {\n if (!resumeKey || autoResumedRef.current.has(resumeKey) || attemptRef.current !== null || isBusy()) return;\n const out = getOutstanding(resumeKey);\n const life = lifeRef.current;\n if (!out || !life) return;\n autoResumedRef.current.add(resumeKey);\n void resumeOutstanding(out, life);\n }, [resumeKey, machine.attempt, isBusy, resumeOutstanding]);\n\n // A different pay-chain override or cross-chain option changes the route: re-read the wallet.\n const routeOptionsKey = `${String(options.payChainId ?? '')}|${JSON.stringify(options.crossChain ?? null)}`;\n const previousRouteOptions = useRef(routeOptionsKey);\n useEffect(() => {\n if (previousRouteOptions.current === routeOptionsKey || isBusy()) return;\n previousRouteOptions.current = routeOptionsKey;\n if (stateRef.current.account) dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }, [routeOptionsKey, machine.attempt, dispatch, isBusy]);\n\n // Quantity changes also change simulation inputs and gas reserve. Invalidate before re-reading.\n const previousQuantity = useRef(machine.quantity);\n useEffect(() => {\n if (previousQuantity.current === machine.quantity || isBusy()) return;\n previousQuantity.current = machine.quantity;\n if (stateRef.current.walletRead) dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }, [machine.quantity, machine.attempt, dispatch, isBusy]);\n\n // Wallet read: bound to the current account, drop and quantity.\n const loadedDrop = machine.drop;\n const walletGen = machine.walletGen;\n const account = machine.account;\n const lastWalletRead = useRef<{ drop: DropInfo; gen: number; quantity: number } | null>(null);\n useEffect(() => {\n if (!account || !loadedDrop || isBusy()) return;\n // A failed preflight remains blocked until its inputs change or a fresh drop arrives.\n const previous = lastWalletRead.current;\n if (stateRef.current.blocked && previous?.drop === loadedDrop && previous.gen === walletGen && previous.quantity === machine.quantity) return;\n lastWalletRead.current = { drop: loadedDrop, gen: walletGen, quantity: machine.quantity };\n const life = lifeRef.current;\n const drop = stateRef.current.drop;\n if (!life || !drop || life.key !== configKeyOf(propsRef.current.config)) return;\n const readGen = life.gen;\n const cfg = propsRef.current.config;\n const adapter = adapterFor(cfg);\n let alive = true;\n void (async () => {\n try {\n const read = await adapter.readWallet(ctxFor(life, cfg), cfg as never, {\n payer: account.address,\n recipient: propsRef.current.recipient ?? account.address,\n quantity: stateRef.current.quantity,\n drop,\n });\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;\n // A preflight revert arrives as data, not a thrown error, so it gets the same public text here.\n const ineligible = read.ineligible?.reason === 'SIMULATION_REVERTED'\n ? { ...read.ineligible, message: friendlyErrorMessage(new MintError('SIMULATION_REVERTED', read.ineligible.message)) }\n : read.ineligible;\n const fullRead = { ...read, ineligible };\n\n // Relay (spec #route-read-idle): only the wallet's own chain is ever quoted or balance-checked (D14).\n const payChains = payChainsOf(cfg, propsRef.current.crossChain);\n const crossChain = payChains.length > 0 && ineligible === null && (adapter.supportsRelay?.(drop) ?? false);\n const routeChain = routeChainOf(payChains, account.chainId, propsRef.current.payChainId);\n const routePending = crossChain && routeChain !== cfg.chainId && payChains.includes(routeChain);\n dispatch({ type: 'WALLET_LOADED', gen: walletGen, read: fullRead, crossChain, routePending });\n if (!crossChain) {\n setCrossChainInfo(null);\n return;\n }\n\n const recipientAddr = propsRef.current.recipient ?? account.address;\n const draft: Attempt = {\n attemptId: 0,\n config: cfg,\n payer: account.address,\n recipient: recipientAddr,\n quantity: stateRef.current.quantity,\n chainId: cfg.chainId,\n tokenContract: tokenContractOf(cfg),\n originChainId: routeChain,\n };\n const testnet = isRelayTestnet(cfg.chainId);\n let tx: TxRequest;\n try {\n tx = (await adapter.buildPlan(ctxFor(life, cfg), draft, fullRead)).mint;\n } catch {\n if (!alive) return;\n setCrossChainInfo(null);\n if (routePending) dispatch({ type: 'ROUTE_LOADED', gen: walletGen, route: { kind: 'direct', chainId: cfg.chainId }, short: null });\n return;\n }\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n setCrossChainInfo((prev) => {\n const same = prev && prev.info.dropChainId === cfg.chainId && prev.info.testnet === testnet && prev.info.tx.to === tx.to\n && prev.info.tx.data === tx.data && prev.info.tx.value === tx.value && prev.info.payer === account.address\n && prev.info.recipient === recipientAddr && prev.info.payChains.join() === payChains.join();\n // Keep the same object when nothing changed, so the picker does not re-quote on a refresh.\n return { gen: walletGen, info: same ? prev.info : { dropChainId: cfg.chainId, payChains, tx, payer: account.address, recipient: recipientAddr, testnet } };\n });\n if (!routePending) return;\n\n // Debounce: quantity steps and refreshes supersede this read through `alive`.\n await new Promise((r) => setTimeout(r, ROUTE_DEBOUNCE_MS));\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n let route: Route = { kind: 'direct', chainId: cfg.chainId };\n let short: PayShort | null = null;\n try {\n const quote = await getQuote({ testnet, payer: account.address, recipient: recipientAddr, originChainId: routeChain, destinationChainId: cfg.chainId, tx, signal: life.controller.signal });\n const relay: Route = { kind: 'relay', originChainId: routeChain, amountIn: quote.amountIn, relayFee: quote.relayFee, originGas: quote.originGas, quotedAt: quote.quotedAt };\n if (routeChain !== account.chainId) {\n route = relay; // payChainId forced: the click switches and checks the balance there.\n } else {\n const balance = await getConnectedBalance(account.address, routeChain);\n if (balance >= quote.amountIn.value + quote.originGas.value) route = relay;\n else short = shortOf(routeChain);\n }\n } catch {\n // Any quote or balance failure only removes the Relay route (N3).\n }\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;\n dispatch({ type: 'ROUTE_LOADED', gen: walletGen, route: short ? null : route, short });\n } catch (e) {\n if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;\n dispatch({ type: 'WALLET_FAILED', gen: walletGen, ...errorReasonOf(e) });\n }\n })();\n return () => {\n alive = false;\n };\n }, [account, loadedDrop, walletGen, machine.quantity, machine.attempt, ctxFor, dispatch, isBusy]);\n\n // Controlled quantity: the prop is the source of truth.\n const prevQuantityProp = useRef(quantityProp);\n useEffect(() => {\n if (isBusy()) return;\n const propChanged = prevQuantityProp.current !== quantityProp;\n prevQuantityProp.current = quantityProp;\n if (quantityProp === undefined || (!propChanged && !propsRef.current.onQuantityChange)) return;\n const state = stateRef.current;\n // Do not oscillate between the supply cap and a tighter wallet cap while wallet reads are pending.\n if (!propChanged && state.account && !state.walletRead) return;\n const event = { type: 'SET_QUANTITY' as const, quantity: quantityProp };\n const clamped = reducer(state, event).quantity;\n if (clamped !== state.quantity) dispatch(event);\n if (clamped !== quantityProp) propsRef.current.onQuantityChange?.(clamped);\n }, [quantityProp, machine.quantity, machine.drop, machine.walletRead, machine.attempt, dispatch, isBusy]);\n\n // Freshness: re-read every 30s and on visibilitychange (spec #state-management).\n useEffect(() => {\n const timer = setInterval(() => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;\n void refreshNow('refresh');\n }, REFRESH_MS);\n const onVisible = (): void => {\n if (document.visibilityState === 'visible') void refreshNow('refresh');\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => {\n clearInterval(timer);\n document.removeEventListener('visibilitychange', onVisible);\n };\n }, [refreshNow]);\n\n // A drop whose walletKey encodes the sale phase (tl-stacks) reads that phase from the latest block, which can lag the\n // wall-clock boundary by a block or more. While the re-read still reports the same key, read again a few seconds\n // later (bounded) instead of waiting for the 30s poll. Drops without a walletKey are never retried.\n const retryWhileStale = useCallback(\n (staleKey: string | undefined, life: Lifetime): void => {\n if (staleKey === undefined) return;\n const step = (tries: number): void => {\n if (tries >= BOUNDARY_RETRY_MAX || !mounted.current || lifeRef.current !== life || stateRef.current.drop?.walletKey !== staleKey) return;\n if (boundaryRetryRef.current) clearTimeout(boundaryRetryRef.current);\n boundaryRetryRef.current = setTimeout(() => {\n boundaryRetryRef.current = null;\n if (!mounted.current || lifeRef.current !== life) return;\n void refreshNow().then(() => step(tries + 1));\n }, BOUNDARY_RETRY_MS);\n };\n step(0);\n },\n [refreshNow],\n );\n\n // Clock: only runs while a start/end boundary is still ahead, so an idle button never re-renders.\n const boundary = machine.drop\n ? [machine.drop.startTime, machine.drop.endTime, machine.drop.phaseEndsAt ?? null].some((t) => t !== null && t > machine.now)\n : false;\n useEffect(() => {\n if (!boundary) return;\n const timer = setInterval(() => {\n const s = stateRef.current;\n const now = nowSec();\n const crossed = s.drop && [s.drop.startTime, s.drop.endTime, s.drop.phaseEndsAt ?? null].some(t => t !== null && t > s.now && t <= now);\n dispatch({ type: 'TICK', now });\n if (crossed) {\n const life = lifeRef.current;\n void refreshNow().then(() => life && retryWhileStale(s.drop?.walletKey, life));\n }\n }, TICK_MS);\n return () => clearInterval(timer);\n }, [boundary, dispatch, refreshNow, retryWhileStale]);\n\n // 'state' events (spec #package-api onEvent payload).\n const prevName = useRef<MintStateName | null>(null);\n useEffect(() => {\n const name = widgetFail ? (isUnsupported(widgetFail.reason) ? 'unsupported' : 'error') : machine.name;\n if (prevName.current === name) return;\n prevName.current = name;\n emit('state', { state: name, reason: widgetFail?.reason ?? machine.reason });\n }, [machine.name, machine.reason, widgetFail, emit]);\n\n // ---------------------------------------------------------------- public state\n\n const setQuantity = useCallback(\n (next: number) => {\n const s = stateRef.current;\n const max = selectMaxQuantity(s) ?? 65535;\n const clamped = Math.max(1, Math.min(Math.floor(Number.isNaN(next) ? 1 : next), max));\n const handler = propsRef.current.onQuantityChange;\n if (handler) {\n handler(clamped); // controlled: the prop drives m.quantity\n return;\n }\n if (attemptRef.current) {\n pendingQuantityRef.current = clamped;\n return;\n }\n dispatch({ type: 'SET_QUANTITY', quantity: clamped });\n if (s.quantity !== stateRef.current.quantity) {\n dispatch({ type: 'ACCOUNT_CHANGED', account: stateRef.current.account });\n }\n },\n [dispatch],\n );\n\n\n const effective: Pick<MachineState, 'name' | 'reason' | 'message'> = widgetFail\n ? { name: isUnsupported(widgetFail.reason) ? 'unsupported' : 'error', reason: widgetFail.reason, message: widgetFail.message }\n : { name: machine.name, reason: machine.reason, message: machine.message };\n\n const wallet: WalletInfo | null =\n machine.account && machine.walletRead\n ? {\n address: machine.account.address,\n chainId: machine.account.chainId,\n recipient: recipient ?? machine.account.address,\n walletMinted: machine.walletRead.walletMinted,\n walletMax: machine.walletRead.walletMax,\n allowlistRemaining: machine.walletRead.allowlistRemaining,\n }\n : null;\n\n return {\n state: effective.name,\n reason: effective.reason,\n message: effective.message,\n drop: machine.drop,\n wallet,\n quantity: machine.quantity,\n setQuantity,\n maxQuantity: selectMaxQuantity(machine),\n total: selectTotal(machine),\n mint,\n result: machine.result,\n refresh,\n // An attempt claimed by a click is not clickable, even while the reducer still reads 'live'.\n canAct: coreCanAct({ ...machine, name: effective.name }) && machine.attempt === null,\n // Short on the wallet chain is neither route: nothing can be paid until the collector picks another chain.\n route: machine.route ?? (machine.walletRead && machine.drop && !machine.routePending && machine.short === null ? { kind: 'direct', chainId: machine.drop.chainId } : null),\n crossChain: machine.crossChain && machine.account && crossChainInfo?.gen === machine.walletGen ? crossChainInfo.info : null,\n };\n}\n","// Relay cross-chain mint: raw HTTP client. Spec: docs/specs/relay-cross-chain-mint.html #relay-client.\n// No SDK, no API key, no referrer (R10): Relay 401s any quote that carries a referrer without a key.\nimport { formatUnits, getAddress, isHex, keccak256, zeroAddress } from 'viem';\nimport type { Address, Hex, Money } from '../types';\nimport type { TxRequest } from '../adapters/types';\nimport { MintError } from '../errors';\n\nexport const RELAY_API = { mainnet: 'https://api.relay.link', testnet: 'https://api.testnets.relay.link' } as const;\n\n/** Drop chains Relay can mint on, and the chains a collector may pay from for each (R2). */\nconst MAINNET_PAY_CHAINS = [1, 8453, 10, 360, 33139] as const;\nconst MAINNET_DROP_CHAINS: readonly number[] = [1, 8453, 10, 360];\nconst SEPOLIA = 11155111;\nconst BASE_SEPOLIA = 84532;\n\nexport const isRelayTestnet = (dropChainId: number): boolean => dropChainId === SEPOLIA;\n\n/** Pay chains for a drop chain, the drop chain included. Empty when Relay cannot mint there. */\nexport function payChainsFor(dropChainId: number): number[] {\n if (MAINNET_DROP_CHAINS.includes(dropChainId)) return [...MAINNET_PAY_CHAINS];\n if (dropChainId === SEPOLIA) return [SEPOLIA, BASE_SEPOLIA];\n return [];\n}\n\nexport type RelayTxItem = { chainId: number; to: Address; data: Hex; value: bigint; gas: bigint | null };\n\nexport type RelayQuote = {\n requestId: Hex;\n originChainId: number;\n items: RelayTxItem[];\n amountIn: Money;\n relayFee: Money;\n originGas: Money;\n quotedAt: number;\n};\n\nexport type QuoteArgs = {\n testnet: boolean;\n payer: Address;\n recipient: Address;\n originChainId: number;\n destinationChainId: number;\n tx: TxRequest;\n signal?: AbortSignal;\n fetch?: typeof fetch;\n /** Skip the 30s cache (click-time quotes, R6). */\n fresh?: boolean;\n};\n\nconst CACHE_MS = 30_000;\nconst cache = new Map<string, { at: number; quote: Promise<RelayQuote> }>();\n\nconst cacheKey = (a: QuoteArgs): string =>\n [a.testnet, a.originChainId, a.destinationChainId, a.tx.to.toLowerCase(), a.tx.value, keccak256(a.tx.data), a.payer.toLowerCase(), a.recipient.toLowerCase()].join(':');\n\n/** Test hook: drop every cached quote. */\nexport function clearQuoteCache(): void {\n cache.clear();\n}\n\nconst big = (v: unknown): bigint => {\n if (typeof v === 'string' && /^\\d+$/.test(v)) return BigInt(v);\n if (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0) return BigInt(v);\n return 0n;\n};\n\ntype Json = Record<string, unknown>;\nconst obj = (v: unknown): Json => (v !== null && typeof v === 'object' ? (v as Json) : {});\n\nfunction moneyOf(amount: unknown, currency: unknown): Money {\n const c = obj(currency);\n const decimals = typeof c.decimals === 'number' ? c.decimals : 18;\n const symbol = typeof c.symbol === 'string' ? c.symbol : 'ETH';\n const value = big(amount);\n return { value, decimals, symbol, formatted: formatUnits(value, decimals) };\n}\n\n/** Strips viem/Relay's \"execution reverted: \" prefix; empty when no reason was given. */\nfunction revertReason(errorData: unknown): string {\n if (typeof errorData !== 'string') return '';\n const text = errorData.replace(/^execution reverted:?\\s*/i, '').trim();\n return text === 'execution reverted' ? '' : text;\n}\n\n/** Response body -> RelayQuote, rejecting any step Dropkit does not know how to execute. */\nexport function parseQuote(body: unknown, originChainId: number): RelayQuote {\n const b = obj(body);\n const steps = Array.isArray(b.steps) ? b.steps.map(obj) : [];\n const unknownStep = () => new MintError('BRIDGE_UNAVAILABLE', 'Relay returned a route this button cannot execute.');\n if (steps.length !== 1 || steps[0]!.kind !== 'transaction') throw unknownStep();\n const step = steps[0]!;\n const rawItems = Array.isArray(step.items) ? step.items.map(obj) : [];\n // Native -> native is one deposit. More items would leave a paid-but-untracked gap between prompts.\n if (rawItems.length !== 1) throw unknownStep();\n const items = rawItems.map((item): RelayTxItem => {\n const d = obj(item.data);\n const chainId = Number(d.chainId);\n if (chainId !== originChainId || typeof d.to !== 'string' || !isHex(d.data)) throw unknownStep();\n return { chainId, to: getAddress(d.to), data: d.data, value: big(d.value), gas: d.gas === undefined ? null : big(d.gas) };\n });\n const requestId = (isHex(step.requestId) ? step.requestId : isHex(b.requestId) ? b.requestId : null) as Hex | null;\n if (!requestId) throw unknownStep();\n const details = obj(b.details);\n const currencyIn = obj(details.currencyIn);\n const fees = obj(b.fees);\n const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);\n if (obj(currencyIn.currency).address !== undefined && obj(currencyIn.currency).address !== zeroAddress) throw unknownStep();\n // The wallet sends the item's value; it must be exactly what the label and the funds check used.\n if (items[0]!.value !== amountIn.value) throw unknownStep();\n return {\n requestId,\n originChainId,\n items,\n amountIn,\n relayFee: moneyOf(obj(fees.relayer).amount, obj(fees.relayer).currency ?? currencyIn.currency),\n originGas: moneyOf(obj(fees.gas).amount, obj(fees.gas).currency ?? currencyIn.currency),\n quotedAt: Math.floor(Date.now() / 1000),\n };\n}\n\n/** Bounds on every Relay HTTP call, so a hung API can never wedge the button (N3). Mutable for tests. */\nexport const RELAY_TIMEOUTS = { quoteMs: 10_000, statusMs: 10_000 };\n\n/** Rejects with AbortError when `signal` aborts; otherwise settles with `p`. */\nfunction raceSignal<T>(p: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return p;\n if (signal.aborted) return Promise.reject(new DOMException('Relay quote cancelled', 'AbortError'));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(new DOMException('Relay quote cancelled', 'AbortError'));\n signal.addEventListener('abort', onAbort, { once: true });\n p.then(\n (v) => {\n signal.removeEventListener('abort', onAbort);\n resolve(v);\n },\n (e: unknown) => {\n signal.removeEventListener('abort', onAbort);\n reject(e instanceof Error ? e : new Error(String(e)));\n },\n );\n });\n}\n\n/**\n * fetch + body read under one timeout (a body that never finishes must not hang the button); never the\n * caller's signal, because the cached request is shared.\n */\nasync function timedFetch(f: typeof fetch, url: string, init: RequestInit, ms: number): Promise<{ ok: boolean; status: number; json: unknown }> {\n const controller = new AbortController();\n let t: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n t = setTimeout(() => {\n controller.abort();\n reject(new DOMException('Relay request timed out', 'TimeoutError'));\n }, ms);\n });\n try {\n return await Promise.race([\n (async () => {\n const res = await f(url, { ...init, signal: controller.signal });\n const json: unknown = await res.json().catch(() => null);\n return { ok: res.ok, status: res.status, json };\n })(),\n timeout,\n ]);\n } finally {\n clearTimeout(t);\n }\n}\n\nasync function requestQuote(a: QuoteArgs): Promise<RelayQuote> {\n const f = a.fetch ?? globalThis.fetch;\n const body = {\n user: a.payer,\n recipient: a.recipient,\n refundTo: a.payer, // R11: without refundTo, Relay disables automatic refunds.\n originChainId: a.originChainId,\n destinationChainId: a.destinationChainId,\n originCurrency: zeroAddress,\n destinationCurrency: zeroAddress,\n tradeType: 'EXACT_OUTPUT',\n amount: a.tx.value.toString(),\n txs: [{ to: a.tx.to, value: a.tx.value.toString(), data: a.tx.data }],\n };\n let res: { ok: boolean; status: number; json: unknown };\n try {\n res = await timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/quote`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, RELAY_TIMEOUTS.quoteMs);\n } catch (e) {\n throw new MintError('BRIDGE_UNAVAILABLE', 'Could not reach Relay for a cross-chain quote.', { cause: e });\n }\n const json = res.json;\n if (res.ok) return parseQuote(json, a.originChainId);\n const j = obj(json);\n if (j.errorCode === 'DESTINATION_TX_FAILED') {\n const reason = revertReason(j.errorData);\n throw new MintError('SIMULATION_REVERTED', reason || 'The mint would fail on the drop network.', { cause: json, domain: 'relay' });\n }\n if (res.status === 401 || j.errorCode === 'UNAUTHORIZED_QUOTE') {\n console.warn('dropkit: Relay rejected the quote (401 UNAUTHORIZED_QUOTE). Relay gates quotes that carry a referrer or need an API key.');\n throw new MintError('BRIDGE_UNAVAILABLE', 'Relay refused the cross-chain quote.', { cause: json });\n }\n if (res.status === 429) throw new MintError('RATE_LIMITED', 'Relay is rate limiting quotes. Try again in a moment.', { cause: json });\n throw new MintError('BRIDGE_UNAVAILABLE', `Relay could not quote this mint (${String(res.status)}).`, { cause: json });\n}\n\n/** POST /quote, EXACT_OUTPUT native -> native with plan.mint as the destination call. Cached 30s unless fresh. */\nexport function getQuote(a: QuoteArgs): Promise<RelayQuote> {\n const k = cacheKey(a);\n const now = Date.now();\n const hit = cache.get(k);\n if (!a.fresh && hit && now - hit.at < CACHE_MS) return raceSignal(hit.quote, a.signal);\n const quote = requestQuote(a);\n cache.set(k, { at: now, quote });\n quote.catch(() => {\n if (cache.get(k)?.quote === quote) cache.delete(k);\n });\n // Each caller may give up on its own; the shared request keeps going for the others.\n return raceSignal(quote, a.signal);\n}\n\nexport type RelayStatus = {\n status: string;\n txHashes: Hex[];\n failReason: string | null;\n refundFailReason: string | null;\n};\n\nconst realReason = (v: unknown): string | null => (typeof v === 'string' && v !== '' && v !== 'N/A' ? v : null);\n\n/** GET /intents/status/v3. The quote's check.endpoint is v1 and is ignored (D9). */\nexport async function getStatus(a: { testnet: boolean; requestId: Hex; signal?: AbortSignal; fetch?: typeof fetch }): Promise<RelayStatus> {\n const f = a.fetch ?? globalThis.fetch;\n const res = await raceSignal(timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/intents/status/v3?requestId=${a.requestId}`, {}, RELAY_TIMEOUTS.statusMs), a.signal);\n if (!res.ok) throw new Error(`Relay status ${String(res.status)}`);\n const j = obj(res.json);\n return {\n status: typeof j.status === 'string' ? j.status : 'unknown',\n txHashes: Array.isArray(j.txHashes) ? j.txHashes.filter((h): h is Hex => isHex(h)) : [],\n failReason: realReason(j.failReason),\n refundFailReason: realReason(j.refundFailReason),\n };\n}\n\nexport const KEEP_POLLING: readonly string[] = ['waiting', 'depositing', 'pending', 'submitted', 'delayed'];\nexport const TERMINAL: readonly string[] = ['success', 'refund', 'failure'];\n","// Polls Relay's v3 status until a terminal status or the cap (spec #execute-click steps 6-8).\nimport type { Hex } from '../types';\nimport { KEEP_POLLING, TERMINAL, getStatus, type RelayStatus } from './client';\n\nexport const POLL_FAST_MS = 2_000;\nexport const POLL_SLOW_MS = 5_000;\nexport const POLL_FAST_FOR_MS = 60_000;\nexport const POLL_CAP_MS = 10 * 60_000;\n\nexport type PollOutcome = { kind: 'terminal'; status: RelayStatus } | { kind: 'timeout' };\n\nexport type PollArgs = {\n testnet: boolean;\n requestId: Hex;\n signal?: AbortSignal;\n fetch?: typeof fetch;\n /** Injected for tests. */\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n now?: () => number;\n /** Throws to stop polling (the attempt was cancelled). */\n assertActive?: () => void;\n};\n\nconst defaultSleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('Relay polling cancelled', 'AbortError'));\n }, { once: true });\n });\n\n/**\n * Polls until success / refund / failure, or POLL_CAP_MS. Every other status, `unknown` and v1's\n * `fallback` included, keeps polling (warned once). HTTP errors and 429s also keep polling: once the\n * deposit exists, only Relay's terminal status may end the attempt (R9).\n */\nexport async function pollUntilTerminal(a: PollArgs): Promise<PollOutcome> {\n const sleep = a.sleep ?? defaultSleep;\n const now = a.now ?? Date.now;\n const start = now();\n let warned = false;\n for (;;) {\n a.assertActive?.();\n try {\n const status = await getStatus({ testnet: a.testnet, requestId: a.requestId, ...(a.signal ? { signal: a.signal } : {}), ...(a.fetch ? { fetch: a.fetch } : {}) });\n if (TERMINAL.includes(status.status)) return { kind: 'terminal', status };\n if (!KEEP_POLLING.includes(status.status) && !warned) {\n warned = true;\n console.warn(`dropkit: Relay status \"${status.status}\" for ${a.requestId}; still waiting.`);\n }\n } catch (e) {\n if ((e as { name?: string } | null)?.name === 'AbortError') throw e;\n // Transient: keep polling.\n }\n a.assertActive?.();\n const elapsed = now() - start;\n if (elapsed >= POLL_CAP_MS) return { kind: 'timeout' };\n await sleep(elapsed < POLL_FAST_FOR_MS ? POLL_FAST_MS : POLL_SLOW_MS, a.signal);\n }\n}\n","// Transient Labs Stacks 721 adapter (spec tl-stacks-drops). Sells a TL drop through purchase() on Stacks v2.3.x:\n// the price and the wallet limit depend on the phase, the buyer pays quantity * (cost + protocolFee), and a presale\n// needs a merkle proof built from the drop's published allowlist file. No React.\nimport {\n BaseError,\n ContractFunctionRevertedError,\n ContractFunctionZeroDataError,\n InsufficientFundsError,\n decodeErrorResult,\n encodeFunctionData,\n formatUnits,\n getAddress,\n isAddress,\n zeroAddress,\n type Hex,\n} from 'viem';\nimport type { Address, Attempt, DropInfo, Money, TlStacksConfig } from '../../types';\nimport type { AdapterContext, MintAdapter, MintPlan, WalletRead } from '../types';\nimport { MintError, isMintError } from '../../errors';\nimport { MAX_QUANTITY } from '../../machine';\nimport { DROP_PHASE, SUPPORTED_VERSIONS, stacksAbi } from './abis';\nimport { assertRoot, buildProof, findEntry, loadAllowlist } from './allowlist';\n\n/** Gas kept aside when the purchase cannot be estimated: a base cost plus a per-token cost for externalMint. */\nconst FALLBACK_GAS_BASE = 150_000n;\nconst FALLBACK_GAS_PER_TOKEN = 150_000n;\n\n/** What buildPlan needs from the reads the button already has (D11: buildPlan never reads the chain). */\nexport type TlWalletData = {\n presaleNumberCanMint: bigint;\n proof: readonly Hex[];\n unitPrice: bigint;\n unitFee: bigint;\n};\n\n/** The drop fields readWallet needs beyond DropInfo, captured by the same read that produced the DropInfo. */\ntype DropDetail = {\n phase: number;\n presaleMerkleRoot: Hex;\n allowance: bigint;\n presaleDuration: bigint;\n publicDuration: bigint;\n startTime: bigint;\n};\nconst details = new WeakMap<DropInfo, DropDetail>();\n\n// ---------- config ----------\n\nfunction bad(field: string, message: string): never {\n throw new MintError('INVALID_CONFIG', `${field} ${message}`, { field });\n}\n\nexport function validateTlStacksConfig(config: TlStacksConfig): void {\n if (!Number.isInteger(config.chainId) || config.chainId <= 0) bad('chainId', 'must be a positive integer');\n if (typeof config.stacks !== 'string' || !isAddress(config.stacks, { strict: false })) bad('stacks', 'is not a valid address');\n if (typeof config.nftContract !== 'string' || !isAddress(config.nftContract, { strict: false })) bad('nftContract', 'is not a valid address');\n if (config.allowlistUrl !== undefined) {\n const https = (() => {\n try {\n return typeof config.allowlistUrl === 'string' && new URL(config.allowlistUrl).protocol === 'https:';\n } catch {\n return false;\n }\n })();\n if (!https) bad('allowlistUrl', 'must be an https: URL');\n }\n}\n\n// ---------- chain reads ----------\n\nconst abortError = (): Error => new DOMException('The operation was aborted', 'AbortError');\nconst isAbortError = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\nfunction throwIfAborted(ctx: AdapterContext): void {\n if (ctx.signal?.aborted) throw abortError();\n}\n\n/** Runs one client operation; an abort rejects promptly even if the transport never settles. */\nfunction io<T>(ctx: AdapterContext, start: () => Promise<T>): Promise<T> {\n const signal = ctx.signal;\n if (!signal) return start();\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener('abort', onAbort, { once: true });\n start().then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n });\n}\n\nconst shortMessage = (e: unknown): string => (e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e));\n\nfunction rpcError(e: unknown): never {\n if (isMintError(e) || isAbortError(e)) throw e;\n throw new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\n/** A call that reverts or returns no data means `stacks` is not a Stacks contract; anything else is the RPC's. */\nasync function readStacks<T>(ctx: AdapterContext, config: TlStacksConfig, functionName: string, args: readonly unknown[] = []): Promise<T> {\n try {\n return (await io(ctx, () =>\n ctx.client.readContract({ address: getAddress(config.stacks), abi: stacksAbi, functionName, args } as never),\n )) as T;\n } catch (e) {\n if (isMintError(e) || isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {\n throw new MintError('INVALID_CONFIG', `stacks ${functionName}() reverted or returned no data. Is ${config.stacks} a TL Stacks 721 contract on chain ${config.chainId}?`, {\n field: 'stacks',\n cause: e,\n });\n }\n return rpcError(e);\n }\n}\n\n/** VERSION() is read once per cacheScope and Stacks address; a failed read is retried. */\nconst versions = new WeakMap<object, Map<string, Promise<string>>>();\n\nasync function checkVersion(ctx: AdapterContext, config: TlStacksConfig): Promise<void> {\n const load = () => readStacks<string>(ctx, config, 'VERSION');\n let version: Promise<string>;\n if (ctx.cacheScope) {\n let byStacks = versions.get(ctx.cacheScope);\n if (!byStacks) versions.set(ctx.cacheScope, (byStacks = new Map()));\n const key = `${config.chainId}:${config.stacks.toLowerCase()}`;\n const cache = byStacks;\n let hit = cache.get(key);\n if (!hit) {\n const p = load();\n cache.set(key, (hit = p));\n p.catch(() => {\n if (cache.get(key) === p) cache.delete(key);\n });\n }\n version = hit;\n } else {\n version = load();\n }\n const v = await version;\n if (!SUPPORTED_VERSIONS.includes(v)) {\n bad('stacks', `is TL Stacks ${v}. Dropkit supports ${SUPPORTED_VERSIONS.join(' and ')} only.`);\n }\n}\n\n// ---------- drop ----------\n\nconst money = (value: bigint, decimals: number, symbol: string): Money => ({ value, decimals, symbol, formatted: formatUnits(value, decimals) });\n\nfunction nativeMeta(ctx: AdapterContext): { decimals: number; symbol: string } {\n const n = ctx.client.chain?.nativeCurrency;\n return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? 'ETH' };\n}\n\nfunction seconds(v: bigint, field: string): number {\n if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError('INVALID_CONFIG', `${field} is too large for a unix timestamp`, { field });\n return Number(v);\n}\n\ntype RawDrop = {\n payoutReceiver: Address;\n initialSupply: bigint;\n supply: bigint;\n allowance: bigint;\n currencyAddress: Address;\n startTime: bigint;\n presaleDuration: bigint;\n presaleCost: bigint;\n presaleMerkleRoot: Hex;\n publicDuration: bigint;\n publicCost: bigint;\n};\n\n/** The first phase's price before the start; the presale's price while it runs; the public price otherwise. */\nfunction priceFor(drop: RawDrop, phase: number): { presalePrice: boolean; unitPrice: bigint } {\n const presalePrice = phase === DROP_PHASE.PRESALE || (phase === DROP_PHASE.NOT_STARTED && drop.presaleDuration > 0n);\n return { presalePrice, unitPrice: presalePrice ? drop.presaleCost : drop.publicCost };\n}\n\nasync function readDrop(ctx: AdapterContext, config: TlStacksConfig): Promise<DropInfo> {\n validateTlStacksConfig(config);\n throwIfAborted(ctx);\n await checkVersion(ctx, config);\n const nft = getAddress(config.nftContract);\n const [drop, phaseRaw, fee] = await Promise.all([\n readStacks<RawDrop>(ctx, config, 'getDrop', [nft]),\n readStacks<number | bigint>(ctx, config, 'getDropPhase', [nft]),\n readStacks<bigint>(ctx, config, 'protocolFee'),\n ]);\n const phase = Number(phaseRaw);\n if (phase === DROP_PHASE.NOT_CONFIGURED) {\n throw new MintError('DROP_NOT_FOUND', 'No Transient Labs drop is configured for this NFT contract on this chain.');\n }\n if (drop.currencyAddress !== zeroAddress) {\n bad('nftContract', `is sold for an ERC-20 (${drop.currencyAddress}). Dropkit supports drops priced in the native token only.`);\n }\n\n const { presalePrice, unitPrice } = priceFor(drop, phase);\n const startTime = seconds(drop.startTime, 'startTime');\n const presaleEnd = seconds(drop.startTime + drop.presaleDuration, 'presaleDuration');\n const soldOut = drop.supply === 0n;\n // TL accepts initialSupply = supply = 0, and _getDropPhase returns ENDED for it from the start. The shared machine\n // reads maxSupply 0 as an open edition, so such a drop gets an end time in the past instead: the button shows\n // `ended`, never a live mint (PR #15 review F11).\n const neverMintable = drop.initialSupply === 0n;\n const native = nativeMeta(ctx);\n\n const info: DropInfo = {\n chainId: config.chainId,\n unitPrice: money(unitPrice, native.decimals, native.symbol),\n unitFee: money(fee, native.decimals, native.symbol),\n totalMinted: drop.initialSupply - drop.supply,\n maxSupply: drop.initialSupply,\n // A never-mintable drop has no start either, so the hook clock has no boundary ahead to tick towards (review F13).\n startTime: neverMintable || startTime === 0 ? null : startTime,\n // At supply 0 the drop is sold out for good: no end time, so it never turns into `ended` (D14).\n endTime: neverMintable ? 0 : soldOut ? null : seconds(drop.startTime + drop.presaleDuration + drop.publicDuration, 'publicDuration'),\n isAllowlist: phase === DROP_PHASE.PRESALE,\n paymentToken: null,\n pricePer: 'unit',\n phaseEndsAt: presalePrice ? presaleEnd : null,\n walletKey: `${phase}:${drop.presaleMerkleRoot}:${unitPrice}:${fee}`,\n };\n details.set(info, {\n phase,\n presaleMerkleRoot: drop.presaleMerkleRoot,\n allowance: drop.allowance,\n presaleDuration: drop.presaleDuration,\n publicDuration: drop.publicDuration,\n startTime: drop.startTime,\n });\n return info;\n}\n\n/** The detail captured with this DropInfo; a copy of it (spread, deserialized) is read again. */\nasync function detailOf(ctx: AdapterContext, config: TlStacksConfig, drop: DropInfo): Promise<DropDetail> {\n const hit = details.get(drop);\n if (hit) return hit;\n const nft = getAddress(config.nftContract);\n const [raw, phase] = await Promise.all([\n readStacks<RawDrop>(ctx, config, 'getDrop', [nft]),\n readStacks<number | bigint>(ctx, config, 'getDropPhase', [nft]),\n ]);\n return {\n phase: Number(phase),\n presaleMerkleRoot: raw.presaleMerkleRoot,\n allowance: raw.allowance,\n presaleDuration: raw.presaleDuration,\n publicDuration: raw.publicDuration,\n startTime: raw.startTime,\n };\n}\n\n// ---------- wallet ----------\n\nconst dayTime = (unix: number): string =>\n new Date(unix * 1000).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });\n\nfunction notOnAllowlist(d: DropDetail): { reason: 'NOT_ON_ALLOWLIST'; message: string } {\n const opens = Number(d.startTime + d.presaleDuration);\n const message =\n d.publicDuration > 0n\n ? `This wallet is not on the allowlist. The public sale opens ${dayTime(opens)}.`\n : 'This wallet is not on the allowlist. This drop is allowlist only.';\n return { reason: 'NOT_ON_ALLOWLIST', message };\n}\n\nasync function readWallet(\n ctx: AdapterContext,\n config: TlStacksConfig,\n { payer, recipient, quantity, drop }: { payer: Address; recipient: Address; quantity: number; drop: DropInfo },\n): Promise<WalletRead> {\n validateTlStacksConfig(config);\n throwIfAborted(ctx);\n const detail = await detailOf(ctx, config, drop);\n const nft = getAddress(config.nftContract);\n const presale = detail.phase === DROP_PHASE.PRESALE;\n\n const [minted, native, gasPrice, list] = await Promise.all([\n readStacks<bigint>(ctx, config, 'getNumberMinted', [nft, getAddress(recipient)]),\n io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),\n io(ctx, () => ctx.client.getGasPrice()).catch(rpcError),\n presale\n ? loadAllowlist({ scope: ctx.cacheScope ?? drop, url: config.allowlistUrl, fetch: ctx.fetch, signal: ctx.signal })\n : Promise.resolve(null),\n ]);\n\n let walletMax = detail.allowance;\n let proof: Hex[] = [];\n let presaleNumberCanMint = 0n;\n let ineligible: WalletRead['ineligible'] = null;\n let allowlistRemaining: number | null = null;\n\n if (presale && list) {\n assertRoot(list, detail.presaleMerkleRoot);\n const entry = findEntry(list, recipient);\n if (!entry) {\n walletMax = 0n;\n ineligible = notOnAllowlist(detail);\n } else {\n walletMax = entry.allowance;\n presaleNumberCanMint = entry.allowance;\n proof = buildProof(list, recipient, entry);\n }\n }\n\n const remaining = walletMax > minted ? walletMax - minted : 0n;\n if (presale && ineligible === null) {\n allowlistRemaining = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));\n if (remaining === 0n) ineligible = { reason: 'ALLOWLIST_USED', message: `This wallet has already minted its allowlist allocation of ${walletMax}.` };\n } else if (!presale && remaining === 0n) {\n ineligible = { reason: 'WALLET_LIMIT', message: `This wallet has already minted its limit of ${walletMax}.` };\n }\n const maxQuantity = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));\n\n const data: TlWalletData = { presaleNumberCanMint, proof, unitPrice: drop.unitPrice.value, unitFee: drop.unitFee.value };\n\n // Gas reserve: estimate the real call when the wallet can pay and the sale is open; otherwise a flat guess.\n const qty = BigInt(quantity);\n const value = qty * (data.unitPrice + data.unitFee);\n let gas = FALLBACK_GAS_BASE + FALLBACK_GAS_PER_TOKEN * qty;\n const open = detail.phase === DROP_PHASE.PRESALE || detail.phase === DROP_PHASE.PUBLIC_SALE;\n if (open && ineligible === null && native >= value) {\n try {\n const estimated = await io(ctx, () =>\n ctx.client.estimateGas({ account: payer, to: getAddress(config.stacks), data: encodePurchase(config, recipient, qty, data), value }),\n );\n gas = estimated;\n } catch (e) {\n if (isAbortError(e)) throw e;\n /* keep the flat guess: the click-time simulation reports a real revert */\n }\n }\n\n return {\n walletMinted: minted,\n walletMax,\n allowlistRemaining,\n maxQuantity,\n ineligible,\n balances: { native, erc20: null },\n gasReserve: gas * gasPrice,\n data,\n };\n}\n\n// ---------- plan and simulation ----------\n\nfunction encodePurchase(config: TlStacksConfig, recipient: Address, quantity: bigint, data: TlWalletData): Hex {\n return encodeFunctionData({\n abi: stacksAbi,\n functionName: 'purchase',\n args: [getAddress(config.nftContract), getAddress(recipient), quantity, data.presaleNumberCanMint, [...data.proof]],\n });\n}\n\nfunction walletData(wallet: WalletRead): TlWalletData {\n const d = wallet.data as Partial<TlWalletData> | undefined;\n if (\n !d ||\n typeof d.presaleNumberCanMint !== 'bigint' ||\n typeof d.unitPrice !== 'bigint' ||\n typeof d.unitFee !== 'bigint' ||\n !Array.isArray(d.proof)\n ) {\n throw new MintError('RPC_ERROR', 'The wallet read is missing the data this mint needs. Reload and try again.');\n }\n return d as TlWalletData;\n}\n\nasync function buildPlan(ctx: AdapterContext, attempt: Attempt & { config: TlStacksConfig }, wallet: WalletRead): Promise<MintPlan> {\n const { config, recipient } = attempt;\n validateTlStacksConfig(config);\n throwIfAborted(ctx);\n if (!Number.isInteger(attempt.quantity) || attempt.quantity < 1) {\n throw new MintError('SIMULATION_REVERTED', 'Choose at least one token to mint.');\n }\n // D11: no reads here. The amount is the last drop read; TL refunds an overpayment and reverts an underpayment.\n const data = walletData(wallet);\n const qty = BigInt(attempt.quantity);\n return {\n approve: null,\n mint: {\n to: getAddress(config.stacks),\n data: encodePurchase(config, recipient, qty, data),\n value: qty * (data.unitPrice + data.unitFee),\n },\n tokenContract: getAddress(config.nftContract),\n };\n}\n\nconst TL_MESSAGES: Readonly<Record<string, string>> = {\n NotOnAllowlist: 'This wallet is not on the allowlist for the current sale.',\n YouShallNotMint: 'This drop is not open for minting right now.',\n AlreadyReachedMintAllowance: 'This wallet has already minted its limit.',\n InsufficientFunds: 'The price or fee changed since the page loaded, so the payment is too low. Reload the page and try again.',\n MintZeroTokens: 'Choose at least one token to mint.',\n SanctionedAddress: 'This wallet or the recipient is blocked by a sanctions check, so it cannot mint.',\n EnforcedPause: 'Minting is paused by Transient Labs right now. Try again later.',\n};\n\nfunction revertData(e: unknown): Hex | null {\n for (let cur: unknown = e, depth = 0; cur && depth < 12; depth++) {\n const d = (cur as { data?: unknown }).data;\n if (typeof d === 'string' && d.startsWith('0x') && d.length >= 10) return d as Hex;\n if (d && typeof d === 'object' && typeof (d as { data?: unknown }).data === 'string') {\n const inner = (d as { data: string }).data;\n if (inner.startsWith('0x') && inner.length >= 10) return inner as Hex;\n }\n cur = (cur as { cause?: unknown }).cause;\n }\n return null;\n}\n\n/** Decodes a Stacks revert into plain text. `domain: 'tl-stacks'` keeps the text through the shared error formatter. */\nexport function decodeTlRevert(e: unknown): MintError {\n if (isMintError(e)) return e;\n if (isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {\n return new MintError('INSUFFICIENT_FUNDS', `The wallet cannot cover the mint: ${e.shortMessage}`, { cause: e });\n }\n const data = revertData(e);\n if (data) {\n try {\n const { errorName, args } = decodeErrorResult({ abi: stacksAbi, data });\n const known = TL_MESSAGES[errorName];\n if (known) return new MintError('SIMULATION_REVERTED', known, { cause: e, domain: 'tl-stacks' });\n const shown = (args ?? []).map(String).join(', ');\n return new MintError('SIMULATION_REVERTED', `Transient Labs rejected the mint (${errorName}(${shown})).`, { cause: e, domain: 'tl-stacks' });\n } catch {\n return new MintError('SIMULATION_REVERTED', `Transient Labs rejected the mint (unrecognized error ${data.slice(0, 10)}).`, { cause: e, domain: 'tl-stacks' });\n }\n }\n const reverted = e instanceof BaseError && e.walk((x) => (x as { code?: number }).code === 3 || /execution reverted/i.test((x as Error).message));\n if (reverted) return new MintError('SIMULATION_REVERTED', shortMessage(e), { cause: e });\n return new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\nasync function simulate(ctx: AdapterContext, attempt: Attempt & { config: TlStacksConfig }, plan: MintPlan): Promise<void> {\n throwIfAborted(ctx);\n // buildPlan reads nothing (D11) and readDrop is only as fresh as the last poll. The creator can switch the drop to an\n // ERC-20 (TL then pulls the whole sale in tokens and only needs the protocol fee in ETH, which a plan for native\n // payment satisfies) or change the price between that poll and the click. Check the drop again before the wallet prompt.\n const { config } = attempt;\n const nft = getAddress(config.nftContract);\n const [drop, phaseRaw, fee] = await Promise.all([\n readStacks<RawDrop>(ctx, config, 'getDrop', [nft]),\n readStacks<number | bigint>(ctx, config, 'getDropPhase', [nft]),\n readStacks<bigint>(ctx, config, 'protocolFee'),\n ]);\n if (drop.currencyAddress !== zeroAddress) {\n throw new MintError('SIMULATION_REVERTED', 'This drop is now sold for a token instead of the native currency, so it cannot be minted here. Reload the page.', { domain: 'tl-stacks' });\n }\n const current = BigInt(attempt.quantity) * (priceFor(drop, Number(phaseRaw)).unitPrice + fee);\n if (current !== plan.mint.value) {\n throw new MintError('SIMULATION_REVERTED', TL_MESSAGES.InsufficientFunds!, { domain: 'tl-stacks' });\n }\n try {\n await io(ctx, () => ctx.client.call({ account: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value }));\n } catch (e) {\n throw decodeTlRevert(e);\n }\n}\n\nexport const tlStacksAdapter: MintAdapter<TlStacksConfig> = { readDrop, readWallet, buildPlan, simulate };\n","// The Transient Labs Stacks 721 v2.3.1 surface the adapter calls (Transient-Labs/tl-stacks tag 2.3.1:\n// src/TLStacks721.sol, src/utils/CommonUtils.sol, src/utils/TLStacks721Utils.sol). Custom errors come from\n// DropErrors, SanctionsCompliance (tl-sol-tools) and OpenZeppelin Pausable.\n\n/** DropPhase enum order in CommonUtils.sol. */\nexport const DROP_PHASE = { NOT_CONFIGURED: 0, NOT_STARTED: 1, PRESALE: 2, PUBLIC_SALE: 3, ENDED: 4 } as const;\n\n/** Stacks versions this adapter was written against. */\nexport const SUPPORTED_VERSIONS: readonly string[] = ['2.3.0', '2.3.1'];\n\nexport const stacksAbi = [\n { type: 'function', name: 'VERSION', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'string' }] },\n { type: 'function', name: 'protocolFee', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256' }] },\n {\n type: 'function',\n name: 'getDrop',\n stateMutability: 'view',\n inputs: [{ name: 'nftAddress', type: 'address' }],\n outputs: [\n {\n name: '',\n type: 'tuple',\n components: [\n { name: 'dropType', type: 'uint8' },\n { name: 'payoutReceiver', type: 'address' },\n { name: 'initialSupply', type: 'uint256' },\n { name: 'supply', type: 'uint256' },\n { name: 'allowance', type: 'uint256' },\n { name: 'currencyAddress', type: 'address' },\n { name: 'startTime', type: 'uint256' },\n { name: 'presaleDuration', type: 'uint256' },\n { name: 'presaleCost', type: 'uint256' },\n { name: 'presaleMerkleRoot', type: 'bytes32' },\n { name: 'publicDuration', type: 'uint256' },\n { name: 'publicCost', type: 'uint256' },\n { name: 'decayRate', type: 'int256' },\n { name: 'baseUri', type: 'string' },\n ],\n },\n ],\n },\n {\n type: 'function',\n name: 'getDropPhase',\n stateMutability: 'view',\n inputs: [{ name: 'nftAddress', type: 'address' }],\n outputs: [{ name: '', type: 'uint8' }],\n },\n {\n type: 'function',\n name: 'getNumberMinted',\n stateMutability: 'view',\n inputs: [\n { name: 'nftAddress', type: 'address' },\n { name: 'recipient', type: 'address' },\n ],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n type: 'function',\n name: 'purchase',\n stateMutability: 'payable',\n inputs: [\n { name: 'nftAddress', type: 'address' },\n { name: 'recipient', type: 'address' },\n { name: 'numberToMint', type: 'uint256' },\n { name: 'presaleNumberCanMint', type: 'uint256' },\n { name: 'proof', type: 'bytes32[]' },\n ],\n outputs: [{ name: 'refundAmount', type: 'uint256' }],\n },\n { type: 'error', name: 'MintZeroTokens', inputs: [] },\n { type: 'error', name: 'NotOnAllowlist', inputs: [] },\n { type: 'error', name: 'YouShallNotMint', inputs: [] },\n { type: 'error', name: 'AlreadyReachedMintAllowance', inputs: [] },\n { type: 'error', name: 'InsufficientFunds', inputs: [] },\n { type: 'error', name: 'SanctionedAddress', inputs: [] },\n // OpenZeppelin v5 Pausable: purchase is whenNotPaused.\n { type: 'error', name: 'EnforcedPause', inputs: [] },\n] as const;\n","// Transient Labs presale allowlist (spec tl-stacks-drops #step-4). TL publishes an OpenZeppelin StandardMerkleTree\n// dump for every drop: { format: 'standard-v1', leafEncoding: ['bytes32','uint256'], tree, values }. Each value is\n// [keccak256(abi.encode(wallet)), allowance] with its treeIndex. The browser builds a wallet's proof from it.\n// All HTTP goes through ctx.fetch; the cache and cancel rules follow manifold-claim/merkle.ts.\nimport { concatHex, encodeAbiParameters, getAddress, keccak256, type Hex } from 'viem';\nimport { MintError } from '../../errors';\nimport type { Address } from '../../types';\n\nexport type AllowlistEntry = { allowance: bigint; treeIndex: number };\n\nexport type Allowlist = {\n /** tree[0], lowercased. */\n root: Hex;\n tree: readonly Hex[];\n /** keccak256(abi.encode(wallet)) lowercased -> the entry with the highest allowance. */\n entries: ReadonlyMap<string, AllowlistEntry>;\n};\n\ntype Fetch = typeof fetch;\n\nconst HASH = /^0x[0-9a-fA-F]{64}$/;\nconst UINT = /^\\d+$/;\nconst MAX_UINT256 = 2n ** 256n - 1n;\n\nconst unavailable = (message: string, cause?: unknown): MintError =>\n new MintError('ALLOWLIST_UNAVAILABLE', message, cause === undefined ? undefined : { cause });\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);\n\n// ---------- hashing ----------\n\n/** keccak256(abi.encode(wallet)): the first half of the leaf, and what the file stores. */\nexport const hashWallet = (wallet: Address): Hex => keccak256(encodeAbiParameters([{ type: 'address' }], [getAddress(wallet)]));\n\n/** TLStacks721.purchase: keccak256(bytes.concat(keccak256(abi.encode(hashedRecipient, allowance)))). */\nexport const leafOf = (hashedWallet: Hex, allowance: bigint): Hex =>\n keccak256(concatHex([keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'uint256' }], [hashedWallet, allowance]))]));\n\n/** OpenZeppelin MerkleProof commutative hash: the smaller node (as uint256) goes first. */\nconst hashPair = (a: Hex, b: Hex): Hex => (BigInt(a) < BigInt(b) ? keccak256(concatHex([a, b])) : keccak256(concatHex([b, a])));\n\n/** Folds a proof into a root with sorted-pair hashing. */\nexport const processProof = (leaf: Hex, proof: readonly Hex[]): Hex => proof.reduce<Hex>((node, sibling) => hashPair(node, sibling), leaf);\n\n// ---------- parsing ----------\n\n/** Validates the file shape (not the root: the root changes with the drop, the file is cached by URL). */\nexport function parseAllowlist(body: unknown): Allowlist {\n if (!isRecord(body)) throw unavailable('The allowlist file is not a merkle tree dump.');\n if (body.format !== 'standard-v1') throw unavailable('The allowlist file is in an unsupported format.');\n const enc = body.leafEncoding;\n if (!Array.isArray(enc) || enc.length !== 2 || enc[0] !== 'bytes32' || enc[1] !== 'uint256') {\n throw unavailable('The allowlist file uses an unsupported leaf encoding.');\n }\n const { tree, values } = body;\n if (!Array.isArray(tree) || tree.length === 0 || !tree.every((n) => typeof n === 'string' && HASH.test(n))) {\n throw unavailable('The allowlist file has no readable merkle tree.');\n }\n if (!Array.isArray(values)) throw unavailable('The allowlist file has no wallet values.');\n\n const entries = new Map<string, AllowlistEntry>();\n for (const row of values) {\n const value = isRecord(row) ? row.value : undefined;\n const treeIndex = isRecord(row) ? row.treeIndex : undefined;\n if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string' || !HASH.test(value[0])) {\n throw unavailable('The allowlist file has an unreadable wallet entry.');\n }\n const raw = value[1];\n const allowanceText = typeof raw === 'string' ? raw : typeof raw === 'number' && Number.isSafeInteger(raw) ? String(raw) : '';\n if (!UINT.test(allowanceText) || BigInt(allowanceText) > MAX_UINT256) throw unavailable('The allowlist file has an unreadable allowance.');\n if (typeof treeIndex !== 'number') throw unavailable('The allowlist file has an unreadable tree index.');\n const key = value[0].toLowerCase();\n const allowance = BigInt(allowanceText);\n const prev = entries.get(key);\n // A wallet can appear once. If it appears more than once, the highest allowance wins.\n if (!prev || allowance > prev.allowance) entries.set(key, { allowance, treeIndex });\n }\n return { root: (tree[0] as string).toLowerCase() as Hex, tree: tree as Hex[], entries };\n}\n\n/** The recipient's entry, or null when the wallet is not on the list. */\nexport const findEntry = (list: Allowlist, recipient: Address): AllowlistEntry | null => list.entries.get(hashWallet(recipient).toLowerCase()) ?? null;\n\n/** The file must belong to the drop: its root has to equal the on-chain presaleMerkleRoot. */\nexport function assertRoot(list: Allowlist, presaleMerkleRoot: Hex): void {\n if (list.root !== presaleMerkleRoot.toLowerCase()) {\n throw unavailable('The allowlist file does not match this drop. The creator may have updated the allowlist; reload the page.');\n }\n}\n\n// ---------- proof ----------\n\n/**\n * OpenZeppelin StandardMerkleTree.getProof for the heap-ordered tree array: the sibling of node i is i - 1 for even\n * i and i + 1 for odd i, and the parent is floor((i - 1) / 2). The proof is checked against the leaf and the root\n * with sorted-pair hashing before it is returned, so a bad file never becomes a mint the contract would reject.\n */\nexport const buildProof = (list: Allowlist, recipient: Address, entry: AllowlistEntry): Hex[] => buildProofForHash(list, hashWallet(recipient), entry);\n\n/** Same walk for a hashed wallet, which is all the file itself holds. */\nexport function buildProofForHash(list: Allowlist, hashedWallet: Hex, entry: AllowlistEntry): Hex[] {\n const { tree } = list;\n const { treeIndex } = entry;\n // Index 0 is the root, not a leaf (spec D8): the index must be an integer in [1, tree.length).\n if (!Number.isInteger(treeIndex) || treeIndex < 1 || treeIndex >= tree.length) {\n throw unavailable('The allowlist file has a tree index outside the tree.');\n }\n\n const leaf = leafOf(hashedWallet, entry.allowance);\n if (tree[treeIndex]!.toLowerCase() !== leaf.toLowerCase()) throw unavailable('The allowlist file entry does not match its merkle leaf.');\n\n const proof: Hex[] = [];\n let i = treeIndex;\n while (i > 0) {\n const sibling = i % 2 === 0 ? i - 1 : i + 1;\n const node = tree[sibling];\n if (node === undefined) throw unavailable('The allowlist file has a broken merkle tree.');\n proof.push(node);\n i = Math.floor((i - 1) / 2);\n }\n if (processProof(leaf, proof).toLowerCase() !== list.root) throw unavailable('The allowlist file does not produce a valid proof for this wallet.');\n return proof;\n}\n\n// ---------- loading ----------\n\n/** Per hook/config lifetime, keyed by URL alone (D13): a page open during an allowlist change needs a reload. */\ntype ScopeCache = { signal: AbortSignal | undefined; loads: Map<string, Promise<Allowlist>> };\nconst scopeCaches = new WeakMap<object, ScopeCache>();\n\n/** Reject and evict cancelled loads even if an injected fetch ignores the shared lifetime signal. */\nfunction withSignal<T>(p: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return p;\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(unavailable('The allowlist request was cancelled.', signal.reason));\n p.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n if (signal.aborted) return onAbort();\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** A download that hasn't finished by then fails, so a hung host never leaves the button loading (PR #15 review F12). */\nexport const ALLOWLIST_TIMEOUT_MS = 30_000;\n/** Bone Market's file is 816 KB for 3,401 wallets. Anything larger than this is refused before it is parsed. */\nexport const ALLOWLIST_MAX_BYTES = 10 * 1024 * 1024;\n\nconst tooLarge = () => unavailable(`The allowlist file is larger than ${ALLOWLIST_MAX_BYTES / (1024 * 1024)} MB.`);\n\n/** Reads the body as text, counting bytes as they arrive. Content-Length is optional and untrusted, so it only short-cuts. */\nasync function readCapped(res: Response): Promise<string> {\n const declared = Number(res.headers.get('content-length'));\n if (Number.isFinite(declared) && declared > ALLOWLIST_MAX_BYTES) throw tooLarge();\n if (!res.body) return '';\n const reader = res.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > ALLOWLIST_MAX_BYTES) {\n void reader.cancel().catch(() => undefined);\n throw tooLarge();\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n bytes.set(c, offset);\n offset += c.byteLength;\n }\n return new TextDecoder().decode(bytes);\n}\n\nasync function fetchAndParse(fetchFn: Fetch, url: string, signal: AbortSignal): Promise<Allowlist> {\n let res: Response;\n try {\n signal.throwIfAborted();\n res = await fetchFn(url, { signal });\n } catch (cause) {\n throw unavailable('Could not reach the allowlist file.', cause);\n }\n if (res.status === 429) throw new MintError('RATE_LIMITED', 'The allowlist host is rate limiting requests. Try again shortly.');\n if (!res.ok) throw unavailable(`The allowlist file is not available (HTTP ${res.status}).`);\n let body: unknown;\n try {\n const text = await readCapped(res);\n body = JSON.parse(text);\n } catch (cause) {\n if (cause instanceof MintError) throw cause;\n throw unavailable('The allowlist file could not be read.', cause);\n }\n return parseAllowlist(body);\n}\n\n/**\n * One download with a deadline. The lifetime signal and the deadline both abort the request, and either one settles\n * this call at once, even when an injected fetch ignores its signal, so the timer and the listener never outlive it\n * (review F14).\n */\nasync function download(fetchFn: Fetch, url: string, signal: AbortSignal | undefined): Promise<Allowlist> {\n const controller = new AbortController();\n\n let onAbort: (() => void) | undefined;\n const stopped = new Promise<never>((_, reject) => {\n onAbort = () => {\n const reason: unknown = controller.signal.reason;\n reject(reason instanceof MintError ? reason : unavailable('The allowlist request was cancelled.', reason));\n };\n controller.signal.addEventListener('abort', onAbort, { once: true });\n });\n // The request sees the lifetime's own reason; the caller gets ALLOWLIST_UNAVAILABLE with it as the cause.\n const onLifetimeAbort = () => controller.abort(signal?.reason);\n if (signal?.aborted) onLifetimeAbort();\n else signal?.addEventListener('abort', onLifetimeAbort, { once: true });\n const timer = setTimeout(\n () => controller.abort(unavailable(`The allowlist file did not load within ${ALLOWLIST_TIMEOUT_MS / 1000} seconds. Try again.`)),\n ALLOWLIST_TIMEOUT_MS,\n );\n try {\n return await Promise.race([fetchAndParse(fetchFn, url, controller.signal), stopped]);\n } finally {\n clearTimeout(timer);\n if (onAbort) controller.signal.removeEventListener('abort', onAbort);\n signal?.removeEventListener('abort', onLifetimeAbort);\n }\n}\n\n/** Fetches and parses the allowlist once per cacheScope and URL. */\nexport function loadAllowlist(args: { scope: object; url: string | undefined; fetch: Fetch | undefined; signal: AbortSignal | undefined }): Promise<Allowlist> {\n if (!args.url) {\n return Promise.reject(unavailable('This presale has no allowlist file configured (allowlistUrl). Ask the creator to add it.'));\n }\n if (args.signal?.aborted) return Promise.reject(unavailable('The allowlist request was cancelled.', args.signal.reason));\n const fetchFn: Fetch = args.fetch ?? ((input, init) => globalThis.fetch(input, init));\n let scoped = scopeCaches.get(args.scope);\n // A reused scope with a fresh lifetime must not inherit an aborted load.\n if (!scoped || scoped.signal?.aborted) {\n scoped = { signal: args.signal, loads: new Map() };\n scopeCaches.set(args.scope, scoped);\n }\n const cache = scoped;\n const hit = cache.loads.get(args.url);\n if (hit) return hit;\n const url = args.url;\n const p = withSignal(download(fetchFn, url, cache.signal), cache.signal);\n cache.loads.set(url, p);\n p.catch(() => {\n if (cache.loads.get(url) === p) cache.loads.delete(url);\n });\n return p;\n}\n","// Paid-but-unresolved Relay requests (spec R12), in memory for this page. After BRIDGE_TIMEOUT the next\n// click (or a remounted button) resumes the same request instead of paying again. Released by a\n// terminal Relay status or a reverted deposit. A reload forgets it, same as claim-widgets (accepted).\nimport type { Address, Attempt, Hex, MintConfig } from '../types';\n\nexport type Outstanding = {\n requestId: Hex;\n testnet: boolean;\n originChainId: number;\n originTxHash: Hex;\n /** The frozen attempt that paid. A resume re-uses it verbatim. */\n attempt: Attempt;\n};\n\nconst memory = new Map<string, Outstanding>();\n/** Keys with a Relay deposit being prompted right now, across every button on the page. */\nconst claimed = new Set<string>();\n\nfunction targetOf(config: MintConfig): string {\n switch (config.type) {\n case 'manifold-claim':\n return `${config.creatorContract.toLowerCase()}:${config.instanceId}`;\n case 'contract':\n return config.address.toLowerCase();\n case 'tl-stacks':\n return `stacks:${config.nftContract.toLowerCase()}`;\n default: {\n const unreachable: never = config;\n return unreachable;\n }\n }\n}\n\n/** One drop, one payer. Recipient and quantity are deliberately not part of the key. */\nexport function outstandingKey(config: MintConfig, payer: Address): string {\n return `${String(config.chainId)}:${targetOf(config)}:${payer.toLowerCase()}`;\n}\n\nexport const getOutstanding = (key: string): Outstanding | null => memory.get(key) ?? null;\nexport const setOutstanding = (key: string, out: Outstanding): void => void memory.set(key, out);\nexport const clearOutstanding = (key: string): void => void memory.delete(key);\n\n/** One deposit prompt per drop and payer at a time: two buttons for the same drop must not both pay. */\nexport function claimDeposit(key: string): boolean {\n if (claimed.has(key) || memory.has(key)) return false;\n claimed.add(key);\n return true;\n}\n\nexport const releaseDeposit = (key: string): void => void claimed.delete(key);\n\n/** Test hook: forget every request. */\nexport function resetOutstandingForTests(): void {\n memory.clear();\n claimed.clear();\n}\n","// Interface contract (orchestrator-owned). Implementation: story s6. Spec ContractConfig.\nimport {\n BaseError,\n ContractFunctionRevertedError,\n ContractFunctionZeroDataError,\n InsufficientFundsError,\n decodeErrorResult,\n encodeFunctionData,\n formatUnits,\n getAddress,\n isAddress,\n parseAbi,\n parseUnits,\n type Abi,\n type AbiFunction,\n type AbiParameter,\n type Hex,\n} from 'viem';\nimport type { Address, ArgTemplate, Attempt, ContractConfig, DropInfo, FieldSource, Money } from '../../types';\nimport type { AdapterContext, MintAdapter, MintPlan, WalletRead } from '../types';\nimport { MintError, isMintError } from '../../errors';\nimport { MAX_QUANTITY } from '../../machine';\n\nexport const PLACEHOLDERS = ['{recipient}', '{payer}', '{quantity}', '{unitPrice}', '{totalPrice}'] as const;\n\ntype Placeholder = (typeof PLACEHOLDERS)[number];\n\nconst ERC20_ABI = parseAbi([\n 'function symbol() view returns (string)',\n 'function decimals() view returns (uint8)',\n 'function balanceOf(address) view returns (uint256)',\n 'function allowance(address,address) view returns (uint256)',\n 'function approve(address,uint256) returns (bool)',\n]);\n\n/** Gas units kept aside when the mint cannot be estimated (ERC20-priced mints, estimate failure). */\nconst FALLBACK_MINT_GAS = 300_000n;\n/** Extra gas for the approve that precedes an ERC20-priced mint. */\nconst APPROVE_GAS = 65_000n;\n\nconst PLACEHOLDER_TOKEN = /\\{[A-Za-z_]\\w*\\}/g;\nconst DISPLAY_AMOUNT = /^\\d+(\\.\\d+)?$/;\nconst NUMERIC_TYPE = /^u?int\\d*$/;\n\n// ---------- validation ----------\n\nfunction bad(field: string, message: string): never {\n throw new MintError('INVALID_CONFIG', `${field} ${message}`, { field });\n}\n\nconst isHexAddress = (a: unknown): a is Address => typeof a === 'string' && isAddress(a, { strict: false });\n\nfunction functionsNamed(abi: Abi, name: string): AbiFunction[] {\n return abi.filter((i): i is AbiFunction => i.type === 'function' && i.name === name);\n}\n\nfunction validateTemplate(t: unknown, path: string, allowed: readonly Placeholder[]): void {\n if (typeof t === 'string') {\n for (const token of t.match(PLACEHOLDER_TOKEN) ?? []) {\n if (!(PLACEHOLDERS as readonly string[]).includes(token)) bad(path, `\"${t}\" is not a known placeholder (${token})`);\n if (!allowed.includes(token as Placeholder)) bad(path, `\"${t}\" uses ${token}, which is not available in this field`);\n }\n } else if (Array.isArray(t)) {\n t.forEach((x, i) => validateTemplate(x, `${path}[${i}]`, allowed));\n } else if (typeof t === 'number') {\n if (!Number.isSafeInteger(t)) bad(path, `${t} is not a safe integer; pass larger or fractional values as a bigint or decimal string`);\n } else if (typeof t !== 'bigint' && typeof t !== 'boolean') {\n bad(path, 'must be a string, number, bigint, boolean or array');\n }\n}\n\n// ---------- template vs ABI input types ----------\n\ntype Issue = { path: string; message: string };\n\nconst ARRAY_TYPE = /^(.*)\\[(\\d*)\\]$/;\nconst BYTES_TYPE = /^bytes(\\d*)$/;\nconst INT_TYPE = /^(u?)int(\\d*)$/;\nconst isExactPlaceholder = (t: unknown): t is Placeholder => typeof t === 'string' && (PLACEHOLDERS as readonly string[]).includes(t);\nconst hasPlaceholder = (t: unknown): boolean => typeof t === 'string' && (t.match(PLACEHOLDER_TOKEN) ?? []).length > 0;\n\nfunction intBounds(type: string): { min: bigint; max: bigint } | null {\n const m = INT_TYPE.exec(type);\n if (!m) return null;\n const bits = BigInt(m[2] ? Number(m[2]) : 256);\n return m[1] ? { min: 0n, max: 2n ** bits - 1n } : { min: -(2n ** (bits - 1n)), max: 2n ** (bits - 1n) - 1n };\n}\n\nconst elementParam = (p: AbiParameter, type: string): AbiParameter => ({ ...p, type }) as AbiParameter;\nconst componentsOf = (p: AbiParameter): readonly AbiParameter[] => (p as { components?: readonly AbiParameter[] }).components ?? [];\n\n/** First way template `t` cannot be encoded as ABI input `p`, or null when it fits. */\nfunction typeIssue(t: ArgTemplate, p: AbiParameter, path: string): Issue | null {\n const no = (message: string): Issue => ({ path, message });\n const arr = ARRAY_TYPE.exec(p.type);\n if (arr) {\n if (!Array.isArray(t)) return no(`must be an array for ${p.type}`);\n if (arr[2] !== '' && t.length !== Number(arr[2])) return no(`has ${t.length} item(s) but ${p.type} takes ${arr[2]}`);\n const el = elementParam(p, arr[1]!);\n for (let i = 0; i < t.length; i++) {\n const issue = typeIssue(t[i]!, el, `${path}[${i}]`);\n if (issue) return issue;\n }\n return null;\n }\n if (p.type === 'tuple') {\n const comps = componentsOf(p);\n if (!Array.isArray(t)) return no('must be an array for a tuple');\n if (t.length !== comps.length) return no(`has ${t.length} item(s) but the tuple takes ${comps.length}`);\n for (let i = 0; i < comps.length; i++) {\n const issue = typeIssue(t[i]!, comps[i]!, `${path}[${i}]`);\n if (issue) return issue;\n }\n return null;\n }\n if (Array.isArray(t)) return no(`must not be an array for ${p.type}`);\n if (p.type === 'address') {\n if (isExactPlaceholder(t)) return t === '{recipient}' || t === '{payer}' ? null : no(`${t} is a number, but the abi input is an address`);\n return typeof t === 'string' && !hasPlaceholder(t) && isAddress(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);\n }\n if (p.type === 'bool') return typeof t === 'boolean' ? null : no(`${JSON.stringify(String(t))} is not a boolean`);\n if (p.type === 'string') return typeof t === 'string' ? null : no(`${JSON.stringify(String(t))} is not a string`);\n const bytes = BYTES_TYPE.exec(p.type);\n if (bytes) {\n const ok = typeof t === 'string' && !hasPlaceholder(t) && /^0x([0-9a-fA-F]{2})*$/.test(t) && (!bytes[1] || t.length === 2 + 2 * Number(bytes[1]));\n return ok ? null : no(`${JSON.stringify(String(t))} is not ${bytes[1] ? `${bytes[1]} bytes of` : ''} hex data`);\n }\n const bounds = intBounds(p.type);\n if (bounds) {\n let n: bigint;\n if (isExactPlaceholder(t)) return t === '{recipient}' || t === '{payer}' ? no(`${t} is an address, but the abi input is ${p.type}`) : null;\n if (typeof t === 'bigint') n = t;\n else if (typeof t === 'number') n = BigInt(t);\n else if (typeof t === 'string' && !hasPlaceholder(t) && /^-?\\d+$/.test(t)) n = BigInt(t);\n else return no(`${JSON.stringify(String(t))} is not an integer for ${p.type}`);\n return n >= bounds.min && n <= bounds.max ? null : no(`${n} is out of range for ${p.type}`);\n }\n return no(`abi type ${p.type} is not supported in an argument template`);\n}\n\nconst signature = (f: AbiFunction): string => `${f.name}(${f.inputs.map((i) => i.type).join(',')})`;\n\n/** Placeholders that exist before a wallet is connected. */\nconst NO_PLACEHOLDERS: readonly Placeholder[] = [];\n\n/** Resolves one ABI function by name and by input types compatible with the arg templates. */\nfunction validateCall(\n abi: Abi,\n call: unknown,\n prefix: string,\n kind: 'read' | 'mint',\n allowed: readonly Placeholder[],\n): AbiFunction {\n const c = (call ?? {}) as { functionName?: unknown; args?: unknown };\n const named = typeof c.functionName === 'string' ? functionsNamed(abi, c.functionName) : [];\n if (named.length === 0) bad(`${prefix}.functionName`, `\"${String(c.functionName)}\" is not a function in the abi`);\n if (c.args !== undefined && !Array.isArray(c.args)) bad(`${prefix}.args`, 'must be an array');\n if (kind === 'mint' && c.args === undefined) bad(`${prefix}.args`, 'is required');\n const args = (c.args ?? []) as unknown[];\n args.forEach((a, i) => validateTemplate(a, `${prefix}.args[${i}]`, allowed));\n const sameArity = named.filter((f) => f.inputs.length === args.length);\n if (sameArity.length === 0) {\n const want = [...new Set(named.map((f) => f.inputs.length))].join(' or ');\n bad(`${prefix}.args`, `has ${args.length} argument(s) but \"${String(c.functionName)}\" takes ${want}`);\n }\n const checked = sameArity.map((f) => {\n let issue: Issue | null = null;\n for (let i = 0; i < args.length && !issue; i++) issue = typeIssue(args[i] as ArgTemplate, f.inputs[i]!, `${prefix}.args[${i}]`);\n return { f, issue };\n });\n const fits = checked.filter((x) => !x.issue);\n if (fits.length === 0) {\n const first = checked[0]!.issue!;\n if (sameArity.length === 1) bad(first.path, first.message);\n bad(`${prefix}.args`, `fit none of the overloads ${sameArity.map(signature).join(', ')}: ${first.path} ${first.message}`);\n }\n const roleFits = fits.filter(({ f }) =>\n kind === 'read'\n ? f.stateMutability === 'view' || f.stateMutability === 'pure'\n : f.stateMutability === 'payable' || f.stateMutability === 'nonpayable',\n );\n if (roleFits.length === 0) {\n const name = fits[0]!.f.name;\n if (kind === 'read') bad(`${prefix}.functionName`, `\"${name}\" is not a view or pure function`);\n if (fits.some(({ f }) => f.stateMutability !== 'view' && f.stateMutability !== 'pure')) {\n bad(`${prefix}.functionName`, `\"${name}\" has missing or invalid stateMutability; mint requires payable or nonpayable`);\n }\n bad(`${prefix}.functionName`, `\"${name}\" is a view or pure function and cannot mint`);\n }\n if (roleFits.length > 1) {\n bad(`${prefix}.functionName`, `\"${String(c.functionName)}\" is ambiguous: ${roleFits.map((x) => signature(x.f)).join(' and ')} both accept these arguments`);\n }\n return roleFits[0]!.f;\n}\n\ntype OutputRef = { tuple: boolean; index: number; type: string };\n\n/** Resolves which return value a read field uses. Throws INVALID_CONFIG naming the bad field. */\nfunction resolveOutput(fn: AbiFunction, output: unknown, path: string): OutputRef {\n const outs = fn.outputs;\n if (outs.length === 0) bad(`${path}.functionName`, `\"${fn.name}\" returns nothing`);\n const first = outs[0]!;\n const tuple = outs.length === 1 && first.type === 'tuple';\n const members = tuple ? ((first as { components?: readonly { name?: string; type: string }[] }).components ?? []) : outs;\n const memberNames = members.map((m, i) => m.name || String(i)).join(', ');\n let index: number;\n if (output === undefined) {\n if (members.length !== 1) bad(`${path}.output`, `is required: \"${fn.name}\" returns several values (${memberNames})`);\n index = 0;\n } else if (typeof output === 'number') {\n if (!Number.isInteger(output) || output < 0 || output >= members.length) {\n bad(`${path}.output`, `${output} is not in the abi outputs of \"${fn.name}\" (${memberNames})`);\n }\n index = output;\n } else if (typeof output === 'string') {\n index = members.findIndex((m) => m.name === output);\n if (index < 0) bad(`${path}.output`, `\"${output}\" is not in the abi outputs of \"${fn.name}\" (${memberNames})`);\n } else {\n return bad(`${path}.output`, 'must be an index or a name');\n }\n const type = members[index]!.type;\n if (!NUMERIC_TYPE.test(type)) {\n const reason = `returns ${type}, not a number`;\n if (output === undefined) bad(`${path}.functionName`, `\"${fn.name}\" ${reason}`);\n bad(`${path}.output`, `selects a value that is ${type}, not a number`);\n }\n return { tuple, index, type };\n}\n\nfunction validateField(\n abi: Abi,\n name: string,\n src: unknown,\n allowed: readonly Placeholder[],\n isPrice: boolean,\n): void {\n if (typeof src !== 'object' || src === null) bad(name, 'is required and must be { value } or { read }');\n const s = src as { value?: unknown; unit?: unknown; read?: unknown };\n const hasRead = Object.hasOwn(s, 'read');\n const hasValue = Object.hasOwn(s, 'value');\n if (hasRead && hasValue) bad(name, 'must be exactly one of { value } or { read }, not both');\n if (hasRead) {\n if (typeof s.read !== 'object' || s.read === null) bad(`${name}.read`, 'must be { functionName, args?, output? }');\n if (s.unit !== undefined) bad(name, 'unit is only valid with a string value');\n const fn = validateCall(abi, s.read, `${name}.read`, 'read', allowed);\n resolveOutput(fn, (s.read as { output?: unknown }).output, `${name}.read`);\n } else if (typeof s.value === 'bigint') {\n if (s.unit !== undefined) bad(name, 'unit is only valid with a string value');\n if (s.value < 0n) bad(`${name}.value`, 'must not be negative');\n } else if (typeof s.value === 'string') {\n if (s.unit !== 'display') bad(name, 'string values need unit: \"display\"');\n if (!isPrice) bad(name, 'display units are only valid for price; use a bigint in base units');\n if (!DISPLAY_AMOUNT.test(s.value)) bad(`${name}.value`, `\"${s.value}\" is not a decimal number like \"0.01\"`);\n } else {\n bad(name, 'must be { value: bigint }, { value: string, unit: \"display\" } (price only) or { read: {...} }');\n }\n}\n\nfunction hasNonzeroStaticPrice(price: unknown): boolean {\n if (typeof price !== 'object' || price === null || !Object.hasOwn(price, 'value')) return false;\n const v = (price as { value: unknown }).value;\n return typeof v === 'bigint' ? v > 0n : typeof v === 'string' && /[1-9]/.test(v);\n}\n\n/**\n * Validates an agent-written config before any network call. Throws MintError('INVALID_CONFIG', msg,\n * { field }) naming the exact bad field: unknown mint.functionName / read functionName, unknown\n * placeholder (field \"mint.args[i]\"), arg count mismatch, missing price, read output index/name not in the\n * ABI outputs or not a numeric type, display unit outside price, bad addresses.\n */\nexport function validateContractConfig(config: ContractConfig): void {\n const c = config as Partial<ContractConfig> | null | undefined;\n if (!c || typeof c !== 'object') bad('config', 'must be an object');\n if (c.type !== 'contract') bad('type', 'must be \"contract\"');\n if (!Number.isSafeInteger(c.chainId) || (c.chainId as number) <= 0) bad('chainId', 'must be a positive safe integer');\n if (!isHexAddress(c.address)) bad('address', `\"${String(c.address)}\" is not a valid address`);\n if (c.tokenContract !== undefined && !isHexAddress(c.tokenContract)) {\n bad('tokenContract', `\"${String(c.tokenContract)}\" is not a valid address`);\n }\n if (!Array.isArray(c.abi)) bad('abi', 'must be an abi array');\n const abi = c.abi;\n\n const mintFn = validateCall(abi, c.mint, 'mint', 'mint', PLACEHOLDERS);\n\n if (c.price === undefined || c.price === null) bad('price', 'is required');\n validateField(abi, 'price', c.price, NO_PLACEHOLDERS, true);\n const cur = (c.price as { currency?: unknown }).currency;\n if (cur !== 'native' && !(typeof cur === 'object' && cur !== null && isHexAddress((cur as { address?: unknown }).address))) {\n bad('price.currency', 'must be \"native\" or { address } with a valid ERC20 address');\n }\n if (cur === 'native' && mintFn.stateMutability === 'nonpayable' && hasNonzeroStaticPrice(c.price)) {\n bad('price', `is a native price, but \"${mintFn.name}\" is nonpayable and cannot take value`);\n }\n const per = (c.price as { per?: unknown }).per;\n if (per !== undefined && per !== 'unit' && per !== 'total') bad('price.per', 'must be \"unit\" or \"total\"');\n\n // Drop-level reads happen before a wallet is connected, so wallet placeholders do not exist there.\n validateOptional(abi, 'totalMinted', c.totalMinted, NO_PLACEHOLDERS);\n validateOptional(abi, 'maxSupply', c.maxSupply, NO_PLACEHOLDERS);\n validateOptional(abi, 'startTime', c.startTime, NO_PLACEHOLDERS);\n validateOptional(abi, 'endTime', c.endTime, NO_PLACEHOLDERS);\n validateOptional(abi, 'walletMinted', c.walletMinted, PLACEHOLDERS);\n validateOptional(abi, 'walletMax', c.walletMax, PLACEHOLDERS);\n}\n\nfunction validateOptional(abi: Abi, name: string, src: unknown, allowed: readonly Placeholder[]): void {\n if (src !== undefined) validateField(abi, name, src, allowed, false);\n}\n\n// ---------- templating ----------\n\ntype Vars = { recipient?: Address; payer?: Address; quantity?: bigint; unitPrice?: bigint; totalPrice?: bigint };\n\nfunction varFor(token: string, vars: Vars): bigint | Address {\n const key = token.slice(1, -1) as keyof Vars;\n const v = vars[key];\n if (v === undefined) throw new MintError('INVALID_CONFIG', `placeholder ${token} has no value here`);\n return typeof v === 'string' ? getAddress(v) : v;\n}\n\n/**\n * Fills a template for ABI input `p` (already checked by typeIssue). A string that is exactly one placeholder\n * becomes the typed value; embedded placeholders are stringified; integer literals become bigint.\n */\nfunction fill(t: ArgTemplate, p: AbiParameter, vars: Vars): unknown {\n const arr = ARRAY_TYPE.exec(p.type);\n if (arr && Array.isArray(t)) return t.map((x) => fill(x, elementParam(p, arr[1]!), vars));\n if (p.type === 'tuple' && Array.isArray(t)) return t.map((x, i) => fill(x, componentsOf(p)[i]!, vars));\n if (typeof t === 'number') return BigInt(t);\n if (typeof t !== 'string') return t;\n if (p.type === 'string') return t.replace(PLACEHOLDER_TOKEN, (token) => String(varFor(token, vars)));\n if (isExactPlaceholder(t)) return varFor(t, vars);\n if (intBounds(p.type)) return BigInt(t);\n return t;\n}\n\nconst fillArgs = (args: readonly ArgTemplate[] | undefined, inputs: readonly AbiParameter[], vars: Vars): unknown[] =>\n (args ?? []).map((a, i) => fill(a, inputs[i]!, vars));\n\n/** {unitPrice} is the configured price as reported in DropInfo; {totalPrice} is what the mint costs. */\nconst totalFor = (per: 'unit' | 'total', price: bigint, quantity: bigint): bigint => (per === 'total' ? price : price * quantity);\n\n// ---------- chain reads ----------\n\nfunction shortMessage(e: unknown): string {\n return e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);\n}\n\nconst abortError = (): Error => new DOMException('The operation was aborted', 'AbortError');\nconst isAbortError = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/** Rejects a pre-aborted signal before any work starts. */\nfunction throwIfAborted(ctx: AdapterContext): void {\n if (ctx.signal?.aborted) throw abortError();\n}\n\n/** Runs one client operation; an abort rejects promptly even if the transport never settles. */\nfunction io<T>(ctx: AdapterContext, start: () => Promise<T>): Promise<T> {\n const signal = ctx.signal;\n if (!signal) return start();\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener('abort', onAbort, { once: true });\n start().then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n });\n}\n\nfunction rpcError(e: unknown): never {\n if (isMintError(e) || isAbortError(e)) throw e;\n throw new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\n/** A contract that reverts or returns no data is a config problem; anything else is the RPC's. */\nfunction classifyReadError(e: unknown, field: string): never {\n if (isMintError(e) || isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {\n throw new MintError('INVALID_CONFIG', `${field} read reverted or returned no data: ${e.shortMessage}`, { field, cause: e });\n }\n return rpcError(e);\n}\n\nasync function readCall(\n ctx: AdapterContext,\n params: { address: Address; abi: Abi; functionName: string; args?: readonly unknown[] },\n field: string,\n): Promise<unknown> {\n try {\n return await io(ctx, () => ctx.client.readContract(params as never));\n } catch (e) {\n return classifyReadError(e, field);\n }\n}\n\nfunction toBigInt(v: unknown, field: string): bigint {\n const n = typeof v === 'number' ? BigInt(v) : v;\n if (typeof n !== 'bigint') throw new MintError('INVALID_CONFIG', `${field} did not return a number`, { field });\n if (n < 0n) throw new MintError('INVALID_CONFIG', `${field} returned a negative number`, { field });\n return n;\n}\n\nasync function readField(ctx: AdapterContext, config: ContractConfig, name: string, src: FieldSource, vars: Vars): Promise<bigint> {\n if (Object.hasOwn(src, 'read') && (src as { read?: unknown }).read != null) {\n const read = (src as Extract<FieldSource, { read: unknown }>).read;\n const path = `${name}.read`;\n const fn = validateCall(config.abi, read, path, 'read', PLACEHOLDERS);\n const args = fillArgs(read.args, fn.inputs, vars);\n const ref = resolveOutput(fn, read.output, path);\n const res = await readCall(ctx, { address: config.address, abi: [fn], functionName: fn.name, args }, `${path}.functionName`);\n const members = fn.outputs.length === 1 && !ref.tuple ? [res] : ref.tuple ? tupleMembers(res, fn, ref.index) : (res as readonly unknown[]);\n return toBigInt(members[ref.index], path);\n }\n const value = (src as { value: unknown }).value;\n if (typeof value === 'bigint') return value;\n throw new MintError('INVALID_CONFIG', `${name} display units are only valid for price`, { field: name });\n}\n\n/** viem returns a struct as an object when every component is named, else as an array. */\nfunction tupleMembers(res: unknown, fn: AbiFunction, index: number): unknown[] {\n if (Array.isArray(res)) return res;\n const comps = (fn.outputs[0] as { components?: readonly { name?: string }[] }).components ?? [];\n const out: unknown[] = [];\n out[index] = (res as Record<string, unknown>)[comps[index]?.name ?? ''];\n return out;\n}\n\nconst optField = (ctx: AdapterContext, config: ContractConfig, name: string, src: FieldSource | undefined, vars: Vars) =>\n src ? readField(ctx, config, name, src, vars) : Promise.resolve(null);\n\nfunction toSeconds(v: bigint | null, field: string): number | null {\n if (v === null || v === 0n) return null;\n if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError('INVALID_CONFIG', `${field} is too large for a unix timestamp`, { field });\n return Number(v);\n}\n\n// ---------- currency and price ----------\n\ntype Meta = { token: Address | null; decimals: number; symbol: string };\n\nfunction nativeMeta(ctx: AdapterContext): { decimals: number; symbol: string } {\n const n = ctx.client.chain?.nativeCurrency;\n return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? 'ETH' };\n}\n\nconst erc20Of = (config: ContractConfig): Address | null =>\n config.price.currency === 'native' ? null : getAddress(config.price.currency.address);\n\nasync function currencyMeta(ctx: AdapterContext, config: ContractConfig): Promise<Meta> {\n const token = erc20Of(config);\n if (!token) return { token, ...nativeMeta(ctx) };\n const field = 'price.currency.address';\n const [symbol, decimals] = await Promise.all([\n readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'symbol' }, field),\n readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'decimals' }, field),\n ]);\n if (typeof symbol !== 'string' || typeof decimals !== 'number') {\n throw new MintError('INVALID_CONFIG', `${field} ${token} is not an ERC20 (symbol/decimals unreadable)`, { field });\n }\n return { token, decimals, symbol };\n}\n\nfunction parseDisplay(value: string, decimals: number): bigint {\n const frac = value.split('.')[1] ?? '';\n if (frac.length > decimals) {\n throw new MintError('INVALID_CONFIG', `price.value \"${value}\" has more than ${decimals} decimal places`, { field: 'price.value' });\n }\n return parseUnits(value, decimals);\n}\n\nasync function resolvePrice(ctx: AdapterContext, config: ContractConfig, meta?: Meta): Promise<bigint> {\n const price = config.price;\n if (Object.hasOwn(price, 'read')) return readField(ctx, config, 'price', price, {});\n const value = (price as { value: bigint | string }).value;\n if (typeof value === 'bigint') return value;\n return parseDisplay(value, (meta ?? (await currencyMeta(ctx, config))).decimals);\n}\n\nconst money = (value: bigint, decimals: number, symbol: string): Money => ({\n value,\n decimals,\n symbol,\n formatted: formatUnits(value, decimals),\n});\n\n// ---------- simulation ----------\n\nfunction revertData(e: unknown): Hex | null {\n for (let cur: unknown = e, depth = 0; cur && depth < 12; depth++) {\n const d = (cur as { data?: unknown }).data;\n if (typeof d === 'string' && d.startsWith('0x') && d.length >= 10) return d as Hex;\n if (d && typeof d === 'object' && typeof (d as { data?: unknown }).data === 'string') {\n const inner = (d as { data: string }).data;\n if (inner.startsWith('0x') && inner.length >= 10) return inner as Hex;\n }\n cur = (cur as { cause?: unknown }).cause;\n }\n return null;\n}\n\nfunction describeRevert(abi: Abi, data: Hex): string {\n try {\n const d = decodeErrorResult({ abi, data });\n const args = (d.args ?? []) as readonly unknown[];\n if (d.errorName === 'Error') return String(args[0] ?? 'execution reverted');\n const inputs = ((d as { abiItem?: { inputs?: readonly { name?: string }[] } }).abiItem?.inputs ?? []) as readonly { name?: string }[];\n const shown = args.map((a, i) => (inputs[i]?.name ? `${inputs[i]!.name}: ${String(a)}` : String(a)));\n return `${d.errorName}(${shown.join(', ')})`;\n } catch {\n return `reverted with unrecognized error ${data.slice(0, 10)}`;\n }\n}\n\nfunction toSimulationError(e: unknown, abi: Abi): MintError {\n if (isMintError(e)) return e;\n if (isAbortError(e)) throw e;\n if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {\n return new MintError('INSUFFICIENT_FUNDS', `Payer cannot cover the mint: ${e.shortMessage}`, { cause: e });\n }\n const data = revertData(e);\n if (data) return new MintError('SIMULATION_REVERTED', describeRevert(abi, data), { cause: e });\n const isRevert = e instanceof BaseError && e.walk((x) => (x as { code?: number }).code === 3 || /execution reverted/i.test((x as Error).message));\n if (isRevert) return new MintError('SIMULATION_REVERTED', shortMessage(e), { cause: e });\n return new MintError('RPC_ERROR', `RPC request failed: ${shortMessage(e)}`, { cause: e });\n}\n\ntype MintCall = { payer: Address; to: Address; data: Hex; value: bigint };\n\nasync function simulateMint(ctx: AdapterContext, config: ContractConfig, call: MintCall): Promise<void> {\n try {\n await io(ctx, () => ctx.client.call({ account: call.payer, to: call.to, data: call.data, value: call.value }));\n } catch (e) {\n throw toSimulationError(e, config.abi);\n }\n}\n\nfunction encodeMint(config: ContractConfig, vars: Vars): Hex {\n const fn = validateCall(config.abi, config.mint, 'mint', 'mint', PLACEHOLDERS);\n try {\n return encodeFunctionData({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) as never } as never);\n } catch (e) {\n if (isMintError(e)) throw e;\n throw new MintError('INVALID_CONFIG', `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage(e)}`, { field: 'mint.args', cause: e });\n }\n}\n\n/** Native value goes only to a payable mint; a nonpayable mint with a nonzero native total would always revert. */\nfunction mintValue(config: ContractConfig, token: Address | null, totalPrice: bigint): bigint {\n if (token) return 0n;\n const fn = validateCall(config.abi, config.mint, 'mint', 'mint', PLACEHOLDERS);\n if (fn.stateMutability === 'nonpayable') {\n if (totalPrice > 0n) bad('price', `is ${totalPrice} wei in native currency, but \"${fn.name}\" is nonpayable and cannot take value`);\n return 0n;\n }\n return totalPrice;\n}\n\n// ---------- adapter ----------\n\nasync function readDrop(ctx: AdapterContext, config: ContractConfig): Promise<DropInfo> {\n validateContractConfig(config);\n throwIfAborted(ctx);\n const meta = await currencyMeta(ctx, config);\n const none: Vars = {};\n const [price, totalMinted, maxSupply, start, end] = await Promise.all([\n resolvePrice(ctx, config, meta),\n optField(ctx, config, 'totalMinted', config.totalMinted, none),\n optField(ctx, config, 'maxSupply', config.maxSupply, none),\n optField(ctx, config, 'startTime', config.startTime, none),\n optField(ctx, config, 'endTime', config.endTime, none),\n ]);\n mintValue(config, meta.token, price);\n const native = nativeMeta(ctx);\n return {\n chainId: config.chainId,\n unitPrice: money(price, meta.decimals, meta.symbol),\n unitFee: money(0n, native.decimals, native.symbol),\n totalMinted: totalMinted ?? 0n,\n maxSupply: maxSupply === null || maxSupply === 0n ? null : maxSupply,\n startTime: toSeconds(start, 'startTime'),\n endTime: toSeconds(end, 'endTime'),\n isAllowlist: false,\n paymentToken: meta.token,\n pricePer: config.price.per ?? 'unit',\n };\n}\n\nasync function readWallet(\n ctx: AdapterContext,\n config: ContractConfig,\n { payer, recipient, quantity, drop }: { payer: Address; recipient: Address; quantity: number; drop: DropInfo },\n): Promise<WalletRead> {\n validateContractConfig(config);\n throwIfAborted(ctx);\n const qty = BigInt(quantity);\n const unitPrice = drop.unitPrice.value;\n const totalPrice = totalFor(drop.pricePer, unitPrice, qty);\n const vars: Vars = { payer, recipient, quantity: qty, unitPrice, totalPrice };\n\n const [walletMinted, rawMax, native, erc20] = await Promise.all([\n optField(ctx, config, 'walletMinted', config.walletMinted, vars),\n optField(ctx, config, 'walletMax', config.walletMax, vars),\n io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),\n drop.paymentToken\n ? readCall(ctx, { address: drop.paymentToken, abi: ERC20_ABI, functionName: 'balanceOf', args: [payer] }, 'price.currency.address').then((v) =>\n toBigInt(v, 'price.currency.address'),\n )\n : Promise.resolve(null),\n ]);\n // walletMax 0 = no limit\n const walletMax = rawMax === null || rawMax === 0n ? null : rawMax;\n const remaining = walletMax === null ? null : walletMax > (walletMinted ?? 0n) ? walletMax - (walletMinted ?? 0n) : 0n;\n const maxQuantity = remaining === null ? null : Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));\n\n let ineligible: WalletRead['ineligible'] = null;\n let gas = FALLBACK_MINT_GAS;\n if (remaining === 0n) {\n ineligible = { reason: 'WALLET_LIMIT', message: `This wallet has already minted its limit of ${walletMax}.` };\n } else if (drop.paymentToken === null && native >= totalPrice) {\n // An ERC20-priced mint cannot be simulated before its approve, and a payer who cannot cover the value\n // is reported as insufficient funds by the machine, so only simulate when the call can run.\n const call: MintCall = { payer, to: getAddress(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };\n try {\n await simulateMint(ctx, config, call);\n gas = await io(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {\n throw toSimulationError(e, config.abi);\n });\n } catch (e) {\n if (!isMintError(e) || e.reason !== 'SIMULATION_REVERTED') throw e;\n ineligible = { reason: 'SIMULATION_REVERTED', message: e.message };\n }\n }\n if (drop.paymentToken) gas += APPROVE_GAS;\n const gasPrice = await io(ctx, () => ctx.client.getGasPrice()).catch(rpcError);\n\n return {\n walletMinted,\n walletMax,\n allowlistRemaining: null,\n maxQuantity,\n ineligible,\n balances: { native, erc20 },\n gasReserve: gas * gasPrice,\n };\n}\n\nasync function buildPlan(ctx: AdapterContext, attempt: Attempt & { config: ContractConfig }, wallet: WalletRead): Promise<MintPlan> {\n void wallet;\n const { config, payer, recipient } = attempt;\n validateContractConfig(config);\n throwIfAborted(ctx);\n const qty = BigInt(attempt.quantity);\n const unitPrice = await resolvePrice(ctx, config);\n const totalPrice = totalFor(config.price.per ?? 'unit', unitPrice, qty);\n const to = getAddress(config.address);\n const token = erc20Of(config);\n const mint = {\n to,\n data: encodeMint(config, { payer, recipient, quantity: qty, unitPrice, totalPrice }),\n value: mintValue(config, token, totalPrice),\n };\n let approve: MintPlan['approve'] = null;\n if (token) {\n const allowance = toBigInt(\n await readCall(ctx, { address: token, abi: ERC20_ABI, functionName: 'allowance', args: [payer, to] }, 'price.currency.address'),\n 'price.currency.address',\n );\n if (allowance < totalPrice) {\n approve = {\n token,\n spender: to,\n amount: totalPrice,\n tx: { to: token, data: encodeFunctionData({ abi: ERC20_ABI, functionName: 'approve', args: [to, totalPrice] }), value: 0n },\n };\n }\n }\n return { approve, mint, tokenContract: getAddress(attempt.tokenContract) };\n}\n\nasync function simulate(ctx: AdapterContext, attempt: Attempt & { config: ContractConfig }, plan: MintPlan): Promise<void> {\n throwIfAborted(ctx);\n await simulateMint(ctx, attempt.config, { payer: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });\n}\n\nexport const contractAdapter: MintAdapter<ContractConfig> = { readDrop, readWallet, buildPlan, simulate };\n","// Pay-with picker: a port of claim-widgets RelayNetworkDropdown (spec R13, D15) without ethers,\n// Zustand or Tailwind. Unstyled, like MintButton. Quotes every pay chain only while mounted.\nimport { createElement, useCallback, useEffect, useState, type ReactNode } from 'react';\n\nimport { getConnectedBalance, switchToChain } from '../core/wallet';\nimport { getQuote } from '../core/relay/client';\nimport { chainName, getChain } from '../core/rpc';\nimport { formatUnits } from 'viem';\nimport type { Money } from '../core/types';\nimport type { MintState } from './useMint';\n\n/** claim-widgets refreshes every 20s (RelayNetworkDropdown.tsx:114-119). */\nexport const PICKER_REFRESH_MS = 20_000;\n/** Quantity steps rebuild the tx; wait for them to settle before quoting every chain (N2). */\nexport const PICKER_DEBOUNCE_MS = 500;\nconst RELAY_REASONS = new Set(['BRIDGE_UNAVAILABLE', 'BRIDGE_TIMEOUT', 'BRIDGE_REFUNDED', 'BRIDGE_FAILED']);\n\nexport type PayOption = {\n chainId: number;\n name: string;\n /** The wallet's current chain. */\n selected: boolean;\n /** The drop's own chain: no bridge, no fee (\"None\"). */\n isDropChain: boolean;\n /** Relay's fee to pay from this chain; null on the drop chain or before the quote. */\n relayFee: Money | null;\n /** Total leaving the wallet on this chain; null on the drop chain or before the quote. */\n amountIn: Money | null;\n /** Balance through the wallet, for the selected chain only (spec D14). */\n balance: Money | null;\n loading: boolean;\n /** Pick this chain: asks the wallet to switch to it. */\n select: () => void;\n};\n\nexport type PayWithPickerProps = {\n /** From useMint, or from MintButton's function child. */\n mint: MintState;\n className?: string;\n /** Replaces the default row markup. */\n renderRow?: (row: PayOption) => ReactNode;\n /** Called after a pick, with the chain id, once the wallet switched (or failed to). */\n onSelect?: (chainId: number, error: unknown) => void;\n};\n\ntype Quoted = { relayFee: Money; amountIn: Money } | 'failed';\n\nconst money = (value: bigint, chainId: number): Money => {\n const c = getChain(chainId).nativeCurrency;\n return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits(value, c.decimals) };\n};\n\n/** Rows for every pay chain; a chain whose quote failed is hidden, the drop chain never is. */\nexport function usePayOptions(mint: MintState, onSelect?: (chainId: number, error: unknown) => void): PayOption[] {\n const info = mint.crossChain;\n const current = mint.wallet?.chainId ?? null;\n const [quotes, setQuotes] = useState<Record<number, Quoted | undefined>>({});\n const [balance, setBalance] = useState<{ chainId: number; value: bigint } | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n const t = setInterval(() => setTick((n) => n + 1), PICKER_REFRESH_MS);\n return () => clearInterval(t);\n }, []);\n\n const key = info ? `${info.tx.to}:${info.tx.value}:${info.tx.data}:${info.payer}:${info.recipient}:${info.payChains.join()}` : '';\n // A new tx (quantity, recipient, payer) invalidates every fee shown: back to loading.\n const [quotedKey, setQuotedKey] = useState(key);\n if (quotedKey !== key) {\n setQuotedKey(key);\n setQuotes({});\n }\n useEffect(() => {\n if (!info) return;\n const controller = new AbortController();\n const timer = setTimeout(() => {\n for (const chainId of info.payChains) {\n if (chainId === info.dropChainId) continue;\n getQuote({ testnet: info.testnet, payer: info.payer, recipient: info.recipient, originChainId: chainId, destinationChainId: info.dropChainId, tx: info.tx, signal: controller.signal }).then(\n (q) => {\n if (!controller.signal.aborted) setQuotes((prev) => ({ ...prev, [chainId]: { relayFee: q.relayFee, amountIn: q.amountIn } }));\n },\n (e: unknown) => {\n // Rate limiting says nothing about the chain: keep the row and try again on the next tick.\n if (controller.signal.aborted || (e as { reason?: string } | null)?.reason === 'RATE_LIMITED') return;\n setQuotes((prev) => ({ ...prev, [chainId]: 'failed' }));\n },\n );\n }\n }, PICKER_DEBOUNCE_MS);\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `key` captures every input of `info`.\n }, [key, tick]);\n\n useEffect(() => {\n if (!info || current === null || !mint.wallet) return;\n let alive = true;\n getConnectedBalance(mint.wallet.address, current).then(\n (value) => {\n if (alive) setBalance({ chainId: current, value });\n },\n () => {\n if (alive) setBalance(null);\n },\n );\n return () => {\n alive = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- re-read on chain/account change and on refresh.\n }, [current, mint.wallet?.address, key, tick]);\n\n const select = useCallback(\n (chainId: number) => {\n switchToChain(chainId).then(\n () => onSelect?.(chainId, null),\n (e: unknown) => onSelect?.(chainId, e),\n );\n },\n [onSelect],\n );\n\n if (!info) return [];\n return info.payChains\n .filter((chainId) => chainId === info.dropChainId || quotes[chainId] !== 'failed')\n .map((chainId): PayOption => {\n const q = quotes[chainId];\n const isDropChain = chainId === info.dropChainId;\n const quoted = q && q !== 'failed' ? q : null;\n return {\n chainId,\n name: chainName(chainId),\n selected: chainId === current,\n isDropChain,\n relayFee: isDropChain ? null : (quoted?.relayFee ?? null),\n amountIn: isDropChain ? null : (quoted?.amountIn ?? null),\n balance: balance && balance.chainId === chainId && chainId === current ? money(balance.value, chainId) : null,\n loading: !isDropChain && q === undefined,\n select: () => select(chainId),\n };\n });\n}\n\nfunction defaultRow(row: PayOption): ReactNode {\n const fee = row.isDropChain ? 'None' : row.loading ? '…' : row.relayFee ? `${row.relayFee.formatted} ${row.relayFee.symbol}` : '--';\n const balance = row.balance ? ` · ${row.balance.formatted} ${row.balance.symbol}` : '';\n return `${row.name}${balance} · fee ${fee}`;\n}\n\n/**\n * One button per pay chain, showing Relay's fee (the drop chain shows \"None\") and, for the wallet's\n * current chain only, its balance. Picking a row switches the wallet; the button re-reads the route.\n * Renders nothing when the drop cannot be minted cross-chain for this wallet.\n */\nexport function PayWithPicker(props: PayWithPickerProps): ReactNode {\n const rows = usePayOptions(props.mint, props.onSelect);\n if (rows.length === 0) return null;\n const open = props.mint.state === 'insufficient-funds' || (props.mint.reason !== null && RELAY_REASONS.has(props.mint.reason));\n return createElement(\n 'div',\n { className: props.className, role: 'group', 'aria-label': 'Pay with', 'data-open': open ? 'true' : 'false' },\n rows.map((row) =>\n createElement(\n 'button',\n {\n key: row.chainId,\n type: 'button',\n 'data-chain': String(row.chainId),\n 'data-selected': row.selected ? 'true' : 'false',\n 'data-loading': row.loading ? 'true' : 'false',\n 'aria-pressed': row.selected,\n // Only an idle button may change chains: a click in progress already chose its pay chain.\n disabled: row.selected || !(props.mint.canAct || props.mint.state === 'insufficient-funds' || props.mint.state === 'loading'),\n onClick: row.select,\n },\n props.renderRow ? props.renderRow(row) : defaultRow(row),\n ),\n ),\n );\n}\n","// Story s7: the default rendering surface. Spec #ui-ux-flow (state table) + #package-api.\nimport { createElement, type ReactNode } from 'react';\n\nimport { isAsyncState, useMint, type MintState, type UseMintOptions } from './useMint';\nimport type { MintStateName } from '../core/machine';\nimport { chainName } from '../core/rpc';\n\nexport type MintButtonProps = UseMintOptions & {\n className?: string;\n /** Per-state text override, e.g. { live: 'Collect', 'sold-out': 'Gone' }. */\n labels?: Partial<Record<MintStateName, string>>;\n /** A function child replaces the default markup entirely; m.mint still drives the flow. */\n children?: ReactNode | ((m: MintState) => ReactNode);\n};\n\n/** Spec #ui-ux-flow \"Default label\" column. No CSS ships. */\nexport function defaultLabel(m: MintState): string {\n const symbol = m.total?.erc20?.symbol ?? m.total?.native.symbol ?? m.drop?.unitPrice.symbol ?? '';\n switch (m.state) {\n case 'loading':\n return 'Loading…';\n case 'unsupported':\n return 'Unavailable';\n case 'upcoming': {\n const t = m.drop?.startTime;\n return `Starts ${t ? new Date(t * 1000).toLocaleString() : 'soon'}`;\n }\n case 'ended':\n return 'Ended';\n case 'sold-out':\n return 'Sold out';\n case 'disconnected':\n return 'Connect wallet';\n case 'connecting':\n return 'Connecting…';\n case 'live': {\n const route = m.route;\n if (route?.kind === 'relay') {\n return `Mint · ${route.amountIn.formatted} ${route.amountIn.symbol} on ${chainName(route.originChainId)}`;\n }\n const total = m.total;\n if (!total) return 'Mint';\n const legs = total.erc20 ? [total.erc20, ...(total.native.value > 0n ? [total.native] : [])] : [total.native];\n return `Mint · ${legs.map(leg => `${leg.formatted} ${leg.symbol}`).join(' + ')}`.trimEnd();\n }\n case 'ineligible':\n return 'Not eligible';\n case 'insufficient-funds': {\n const erc20 = m.total?.erc20;\n // Core supplies the failing currency in its funds message; native has priority when both are short.\n const shortage = erc20 && m.message === `Not enough ${erc20.symbol} to cover the mint.`\n ? erc20.symbol : m.total?.native.symbol ?? symbol;\n return `Insufficient ${shortage}`.trimEnd();\n }\n case 'switching-chain':\n return 'Switching network…';\n case 'approving':\n return `Approving ${symbol}…`.replace(' …', '…');\n case 'minting':\n return 'Confirm in wallet…';\n case 'confirming':\n return 'Minting…';\n case 'success':\n return 'Minted';\n case 'error':\n return 'Try again';\n }\n}\n\nexport function MintButton(props: MintButtonProps): ReactNode {\n const { className, labels, children, ...options } = props;\n const m = useMint(options);\n\n if (typeof children === 'function') return (children as (m: MintState) => ReactNode)(m);\n\n const label = labels?.[m.state] ?? defaultLabel(m);\n return createElement(\n 'button',\n {\n type: 'button',\n className,\n 'data-state': m.state,\n ...(m.reason ? { 'data-reason': m.reason } : {}),\n ...(isAsyncState(m.state) ? { 'aria-busy': 'true' } : {}),\n disabled: !m.canAct,\n onClick: m.mint,\n },\n children ?? label,\n );\n}\n","export const VERSION = '0.4.1';\n\n// React surface (story s7).\nexport { MintButton, PayWithPicker, useMint, usePayOptions } from './react';\nexport type { CrossChainInfo, MintButtonProps, MintState, PayOption, PayWithPickerProps, UseMintOptions } from './react';\n\n// Core types an agent writes or reads.\nexport type {\n Address,\n ArgTemplate,\n Attempt,\n ContractConfig,\n CrossChainOption,\n DropInfo,\n Erc20Ref,\n FieldSource,\n Hex,\n ManifoldClaimConfig,\n MintConfig,\n MintEvent,\n MintEventType,\n MintResult,\n MintedToken,\n Money,\n Route,\n RpcUrls,\n TlStacksConfig,\n WalletInfo,\n} from './core/types';\nexport { MINT_STATES, CLICKABLE_STATES, MAX_QUANTITY } from './core/machine';\nexport type { MintStateName } from './core/machine';\nexport { MintError, isMintError, REASON_CODES } from './core/errors';\nexport type { ErrorReason, IneligibleReason, ReasonCode, UnsupportedReason } from './core/errors';\n"]}