@gvnrdao/dh-sdk 0.0.300 → 0.0.303

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.
package/dist/index.js CHANGED
@@ -87,7 +87,8 @@ var init_deployment_addresses = __esm({
87
87
  mockUsdtToken: "0xF8b7fB38b17a310960E47805Be9adba0b0e0394D",
88
88
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
89
89
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
90
- BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe"
90
+ BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
91
+ CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7"
91
92
  },
92
93
  upgraded: {
93
94
  PositionManager: {
@@ -349,7 +350,9 @@ var init_deployment_addresses = __esm({
349
350
  FEE_RECIPIENT_REGISTRY_IMPL: "0xd08513a7cB2dd354B26cDDf549616F12054c34c9",
350
351
  OPERATION_AUTHORIZATION_REGISTRY_IMPL: "0xe0D981B60351f0f8fFDA2b50C8180Fb37CeF3883",
351
352
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
352
- BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0xFF270BC4470dBA80430Bd1a3a66EB691599354fA"
353
+ BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0xFF270BC4470dBA80430Bd1a3a66EB691599354fA",
354
+ CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
355
+ CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01"
353
356
  }
354
357
  };
355
358
  SEPOLIA_CONTRACTS = {
@@ -377,7 +380,8 @@ var init_deployment_addresses = __esm({
377
380
  mockUsdtToken: "0xF8b7fB38b17a310960E47805Be9adba0b0e0394D",
378
381
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
379
382
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
380
- BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe"
383
+ BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
384
+ CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7"
381
385
  };
382
386
  MAINNET_DEPLOYMENT = {
383
387
  network: "mainnet",
@@ -412,10 +416,85 @@ var init_deployment_addresses = __esm({
412
416
  upgraded: {
413
417
  SimplePSMV2: {
414
418
  previousImplementation: "0x1a555FB8D6EDdF755a4575E541651EdB8B49a15A",
415
- newImplementation: "0x1a555FB8D6EDdF755a4575E541651EdB8B49a15A",
416
- upgradedAt: "2026-06-16T17:53:24.979Z",
417
- upgradedBy: "0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f",
418
- reason: "Initial PSM deploy / artifact sync"
419
+ newImplementation: "0x8717761444b1822c2E6916aB5bD9ac96c91b6DF5",
420
+ upgradedAt: "2026-07-06T10:41:35.000Z",
421
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0x5052d1559d56b8e178699183e56c4fd84a163e987365c5af9c8d669a723c35d6 (block 25472960)",
422
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
423
+ executeTx: "0x5052d1559d56b8e178699183e56c4fd84a163e987365c5af9c8d669a723c35d6",
424
+ block: 25472960
425
+ },
426
+ CollateralManagerModule: {
427
+ previousImplementation: "0x1919860e528Cd2AbbD31a17858c986b8aCc8C185",
428
+ newImplementation: "0x55110B9dd6B2c9f812564e7a1ef42b3c76E1A3Bb",
429
+ upgradedAt: "2026-07-08T20:33:59Z",
430
+ upgradedBy: "AdminModule generic-op 0x740efe0cfe85044479cbd8ee3058aa244057d90703dd2b37044dd6787cbf3485 (3-of-5 ADMIN_ROLE Safes, 24h timelock; executed by 0xfb3B8cbeFB7D666A6Af34C53e189EdC31862a7be in tx 0x430c8e450f12d3d9359cde356ac5e2ef39661d7a89884b5674a596c1090f6cff)",
431
+ reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.3.0 -> 1.3.1"
432
+ },
433
+ BitcoinWithdrawalAddressRegistry: {
434
+ previousImplementation: "0x11731bFEC3068F2cc19D016381dfEA3B93013b1C",
435
+ newImplementation: "0x190c08300Ae650fb88ed8156445BBeb13bE9E8a3",
436
+ upgradedAt: "2026-07-08T20:33:59Z",
437
+ upgradedBy: "AdminModule generic-op 0x2603a486c0f34554b864cc1d909b325c7e105331da10ec0fd7e0b9373fa76207 (3-of-5 ADMIN_ROLE Safes, 24h timelock; executed by 0xfb3B8cbeFB7D666A6Af34C53e189EdC31862a7be in tx 0x430c8e450f12d3d9359cde356ac5e2ef39661d7a89884b5674a596c1090f6cff)",
438
+ reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.0.0 -> 1.0.1"
439
+ },
440
+ UCDController: {
441
+ newImplementation: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
442
+ upgradedBy: "Reconciled from chain \u2014 Upgraded event log unavailable; provenance omitted (never fabricated)",
443
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)"
444
+ },
445
+ PositionManagerCoreModule: {
446
+ previousImplementation: "0x6C8b0FB93F8d0239254aefaC24d4264cA2F8AAeB",
447
+ newImplementation: "0x5067Ea1d40b2Aaf4ad693bD9b6e7463C1A29E849",
448
+ upgradedAt: "2026-07-06T08:02:35.000Z",
449
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0x2002827d34A781883Ec4c83c36ECC140c3911F2B in tx 0xeb2bfe77d46394b6d0afd46beb0a80cd9beb91f6771ccb59ff9fba27d4af4f3d (block 25472167)",
450
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
451
+ executeTx: "0xeb2bfe77d46394b6d0afd46beb0a80cd9beb91f6771ccb59ff9fba27d4af4f3d",
452
+ block: 25472167
453
+ },
454
+ LiquidationManagerModule: {
455
+ previousImplementation: "0xc2f4517eeD7c4c516939a64c7938a6ab432D99c4",
456
+ newImplementation: "0xe7444C1d9652dAb31BaAdBE4C7c7e7Df3515dA46",
457
+ upgradedAt: "2026-07-06T10:40:47.000Z",
458
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xc173b97af68cc9172ea0ddc91a2c90bf02eac1505e749d92aa044aa69891f48c (block 25472956)",
459
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
460
+ executeTx: "0xc173b97af68cc9172ea0ddc91a2c90bf02eac1505e749d92aa044aa69891f48c",
461
+ block: 25472956
462
+ },
463
+ BTCSpendAuthorizer: {
464
+ previousImplementation: "0xb003ed89F695E8819a85513fE17260b805B3c478",
465
+ newImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
466
+ upgradedAt: "2026-07-06T10:40:59.000Z",
467
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281 (block 25472957)",
468
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
469
+ executeTx: "0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281",
470
+ block: 25472957
471
+ },
472
+ PositionManager: {
473
+ previousImplementation: "0x9f52BaBB40FbFC5863e45B1A4014cB433950c684",
474
+ newImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
475
+ upgradedAt: "2026-07-06T08:42:47.000Z",
476
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0x2002827d34A781883Ec4c83c36ECC140c3911F2B in tx 0xa1fbe31cb71736514a868e681573dff22c9fab91d69cec157a24a30e29eadbe6 (block 25472368)",
477
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
478
+ executeTx: "0xa1fbe31cb71736514a868e681573dff22c9fab91d69cec157a24a30e29eadbe6",
479
+ block: 25472368
480
+ },
481
+ LoanOperationsManagerModule: {
482
+ previousImplementation: "0x56671D671e492Dae625413d463D47605Cf54edf6",
483
+ newImplementation: "0x7d90aAB4466f59BA9aC9C7a9BF14dC53734336e7",
484
+ upgradedAt: "2026-07-06T10:41:23.000Z",
485
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0x327ae9f7a3917788d22dfe95611e1afcd18583f210b03a119d9f04eca50b9e7a (block 25472959)",
486
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
487
+ executeTx: "0x327ae9f7a3917788d22dfe95611e1afcd18583f210b03a119d9f04eca50b9e7a",
488
+ block: 25472959
489
+ },
490
+ OperationAuthorizationRegistry: {
491
+ previousImplementation: "0x327662917Ac8EEda47851d74AF6A151348636fCe",
492
+ newImplementation: "0x493118746840EDB2D396E71632D2E774CA14070e",
493
+ upgradedAt: "2026-07-06T10:41:47.000Z",
494
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0x9820e259700865a281b8376e248044048ba2fed1d17c811ac66ac54d36711a5b (block 25472961)",
495
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
496
+ executeTx: "0x9820e259700865a281b8376e248044048ba2fed1d17c811ac66ac54d36711a5b",
497
+ block: 25472961
419
498
  }
420
499
  },
421
500
  latestEnv: {
@@ -428,9 +507,37 @@ var init_deployment_addresses = __esm({
428
507
  BITCOIN_PROVIDER_REGISTRY: "0x4ab3Fb96e02893Ed6FAa61bad1918390c8DbbFFC",
429
508
  BITCOIN_PROVIDER_REGISTRY_IMPL: "0x7Bc9473E45fb3EA594e8b13992F57D0444902ddB",
430
509
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY: "0xf667f77FDba7458345f227eB6Bfe4148243E7270",
431
- BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0x11731bFEC3068F2cc19D016381dfEA3B93013b1C",
510
+ BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0x190c08300Ae650fb88ed8156445BBeb13bE9E8a3",
432
511
  SIMPLE_PSM_V2: "0xfE0C14B4E614f40Bf58845b4B04CeD17fd116532",
433
- SIMPLE_PSM_V2_IMPL: "0x1a555FB8D6EDdF755a4575E541651EdB8B49a15A"
512
+ SIMPLE_PSM_V2_IMPL: "0x8717761444b1822c2E6916aB5bD9ac96c91b6DF5",
513
+ UCD_CONTROLLER: "0x4b4f8063048bCD59D469219E3f07C6aaDF4f6157",
514
+ UCD_CONTROLLER_IMPL: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
515
+ UCD_TOKEN: "0xaAaB6B05E5EecB015898483d4e6703d7F218850b",
516
+ UCD_TOKEN_IMPL: "0x5f09C660036021D7e916DF47234C24a2536C3A72",
517
+ POSITION_MANAGER_CORE_MODULE: "0xd895B685914c1BE4e1c7aab9f89a90c203DE8d22",
518
+ POSITION_MANAGER_CORE_MODULE_IMPL: "0x5067Ea1d40b2Aaf4ad693bD9b6e7463C1A29E849",
519
+ TERM_MANAGER_MODULE: "0xDB99CdF883ffa38acD099bC39F8d1f4508c9239E",
520
+ TERM_MANAGER_MODULE_IMPL: "0x971f63f92990e59270E971612dd399fF2C63B0c3",
521
+ COLLATERAL_MANAGER_MODULE: "0x2Eb74FEc478218f5C260aAc78Bb8eC297F9d7135",
522
+ COLLATERAL_MANAGER_MODULE_IMPL: "0x55110B9dd6B2c9f812564e7a1ef42b3c76E1A3Bb",
523
+ LIQUIDATION_MANAGER_MODULE: "0x74B28695352E7bf736D6c9F76ccb7B4439477De9",
524
+ LIQUIDATION_MANAGER_MODULE_IMPL: "0xe7444C1d9652dAb31BaAdBE4C7c7e7Df3515dA46",
525
+ CIRCUIT_BREAKER_MODULE: "0xF7b545d3a4Ee056D06909AB367E2F7bFFE3cB1BC",
526
+ CIRCUIT_BREAKER_MODULE_IMPL: "0x40e8328D61342C6ADC51dfdC723296c908f95038",
527
+ ADMIN_MODULE: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
528
+ ADMIN_MODULE_IMPL: "0xb4C5C7E051A1e672F014B25CB5D55B225Aa0D512",
529
+ BTC_SPEND_AUTHORIZER: "0xfBbF108486fa07F1B06a811C5b66ed68DD9c508a",
530
+ BTC_SPEND_AUTHORIZER_IMPL: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
531
+ POSITION_MANAGER_VIEWS: "0x9225647a30bbACA4Acc0491B786869036914242e",
532
+ POSITION_MANAGER_VIEWS_IMPL: "0x9225647a30bbACA4Acc0491B786869036914242e",
533
+ POSITION_MANAGER: "0x12A1836e56139ccaD8722B89519d84233efc224B",
534
+ POSITION_MANAGER_IMPL: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
535
+ LOAN_OPERATIONS_MANAGER_MODULE: "0x2561974A40F1AEed1251d0A090A83cF8536FB9E9",
536
+ LOAN_OPERATIONS_MANAGER_MODULE_IMPL: "0x7d90aAB4466f59BA9aC9C7a9BF14dC53734336e7",
537
+ UPGRADE_VALIDATOR: "0xC46Ae71F22e3741A6CF46E399aE914C4868fEbed",
538
+ UPGRADE_VALIDATOR_IMPL: "0x4f835e77D1211299a1cbb8Aa2b42d9B390cC95CA",
539
+ OPERATION_AUTHORIZATION_REGISTRY: "0xD4505645b724754f5170a2027804a4265e860d44",
540
+ OPERATION_AUTHORIZATION_REGISTRY_IMPL: "0x493118746840EDB2D396E71632D2E774CA14070e"
434
541
  }
435
542
  };
436
543
  MAINNET_CONTRACTS = {
@@ -461,44 +568,44 @@ var init_deployment_addresses = __esm({
461
568
  LOCALHOST_DEPLOYMENT = {
462
569
  network: "localhost",
463
570
  chainId: 31337,
464
- timestamp: "2026-03-23T15:33:34.068Z",
571
+ timestamp: "2026-06-11T12:30:59.589Z",
465
572
  deployer: "",
466
573
  contracts: {
467
- MessageHashBuilder: "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E",
468
- UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
574
+ MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
575
+ UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
469
576
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
470
577
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
471
- PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
472
- TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
473
- LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
578
+ PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
579
+ TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
580
+ LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
474
581
  BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
475
582
  CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
476
583
  LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
477
- CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
584
+ CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
478
585
  AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
479
- PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
480
- PositionManager: "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB",
481
- OperationAuthorizationRegistry: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0",
482
- PKPValidation: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc"
586
+ PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
587
+ PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
588
+ OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
589
+ PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
483
590
  }
484
591
  };
485
592
  LOCALHOST_CONTRACTS = {
486
- MessageHashBuilder: "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E",
487
- UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
593
+ MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
594
+ UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
488
595
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
489
596
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
490
- PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
491
- TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
492
- LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
597
+ PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
598
+ TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
599
+ LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
493
600
  BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
494
601
  CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
495
602
  LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
496
- CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
603
+ CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
497
604
  AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
498
- PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
499
- PositionManager: "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB",
500
- OperationAuthorizationRegistry: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0",
501
- PKPValidation: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc"
605
+ PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
606
+ PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
607
+ OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
608
+ PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
502
609
  };
503
610
  ALL_DEPLOYMENTS = {
504
611
  sepolia: SEPOLIA_DEPLOYMENT,
@@ -1190,30 +1297,30 @@ var require_pkg_src = __commonJS({
1190
1297
  }
1191
1298
  },
1192
1299
  btcTransactionSigner: {
1193
- cid: "QmZs56sVTCJv7mgvyRYfpYHFvFLcSeXa4H44qUYT7z14QP",
1300
+ cid: "QmSJkQAw5SJ39EGEwzWZJ39ApNN4834VgKS5rhm25YCgfk",
1194
1301
  authorizedCidHex: cidToHex(
1195
- "QmZs56sVTCJv7mgvyRYfpYHFvFLcSeXa4H44qUYT7z14QP"
1302
+ "QmSJkQAw5SJ39EGEwzWZJ39ApNN4834VgKS5rhm25YCgfk"
1196
1303
  ),
1197
1304
  name: "Btc Transaction Signer",
1198
1305
  description: "Production Btc Transaction Signer",
1199
- version: "1.0.0",
1306
+ version: "1.1.1",
1200
1307
  deployed: true,
1201
- deployedAt: 1781542204806,
1202
- size: 42376,
1203
- hash: "f4aed3d88f6fbff6623ed2047436cecd4f561a74883171ea6f4ebe0a1129973c"
1308
+ deployedAt: 1784316496778,
1309
+ size: 63679,
1310
+ hash: "b12aa725797af5ef07f8e6945f05741f583d6e479a4670b1121e97c58fe63191"
1204
1311
  },
1205
1312
  ucdMintValidator: {
1206
- cid: "QmPir8F8jmJ4ssQpEC3xFxkEnZxMHcgS6VxBQE1jkTLj5K",
1313
+ cid: "QmYVRMfiSC641JPbE819szSDw3YrG6uSrZc27CZkyYTGtW",
1207
1314
  authorizedCidHex: cidToHex(
1208
- "QmPir8F8jmJ4ssQpEC3xFxkEnZxMHcgS6VxBQE1jkTLj5K"
1315
+ "QmYVRMfiSC641JPbE819szSDw3YrG6uSrZc27CZkyYTGtW"
1209
1316
  ),
1210
1317
  name: "Ucd Mint Validator",
1211
1318
  description: "Production Ucd Mint Validator",
1212
- version: "1.0.0",
1319
+ version: "1.2.1",
1213
1320
  deployed: true,
1214
- deployedAt: 1781542207366,
1215
- size: 72074,
1216
- hash: "567a51d3beaedbda4f0af2636835ca8a2af89c2264e91f8dc31f522c1a424b7b",
1321
+ deployedAt: 1784316505431,
1322
+ size: 80381,
1323
+ hash: "132a0a8e6e9a1b85d02e00c0e57eaa371d825717255a28c9e644e4b521760026",
1217
1324
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1218
1325
  pkp: {
1219
1326
  publicKey: "0x041ab17cd91fc5c0b761eea6092d032807561b621b82c488826776e04a9158d61ba64d809a4729f0501289d980732cb06d8dfd06999dd9c8efd2f495dad78b31bb",
@@ -1221,17 +1328,17 @@ var require_pkg_src = __commonJS({
1221
1328
  }
1222
1329
  },
1223
1330
  processPaymentValidator: {
1224
- cid: "QmVq19NXJEpMMvaWQdsU5czLoqh28WGWoGgKMSYFaCmfa9",
1331
+ cid: "QmadmdFDH6sS7YLusUuuLkkt7n51iUj1bJ5KKTLhmro2iQ",
1225
1332
  authorizedCidHex: cidToHex(
1226
- "QmVq19NXJEpMMvaWQdsU5czLoqh28WGWoGgKMSYFaCmfa9"
1333
+ "QmadmdFDH6sS7YLusUuuLkkt7n51iUj1bJ5KKTLhmro2iQ"
1227
1334
  ),
1228
1335
  name: "Process Payment Validator",
1229
1336
  description: "Production Process Payment Validator",
1230
- version: "1.0.0",
1337
+ version: "1.2.1",
1231
1338
  deployed: true,
1232
- deployedAt: 1781542209526,
1233
- size: 68239,
1234
- hash: "ada27fea57aa3f3b861c49e75ed1f80b72500fdccc1d6e88ee1a3c1396b81867",
1339
+ deployedAt: 1784316520125,
1340
+ size: 76534,
1341
+ hash: "dbdf43d3f1636e0ee883d349ca28f24c5c38ed7e062b76ca75498aaf5fe594ea",
1235
1342
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1236
1343
  pkp: {
1237
1344
  publicKey: "0x04cfccb9b6c7addc79469c0e9fafebef4c70801302c62cdb26b610723a9b8527d23f98a40158c7340d83fd0e62d02b5ec74c12f7a86d98e8c6973b4811110f19ad",
@@ -1239,17 +1346,17 @@ var require_pkg_src = __commonJS({
1239
1346
  }
1240
1347
  },
1241
1348
  extendPositionValidator: {
1242
- cid: "QmP2u3bgKLtfqsFMp6Li72zMY8ZZH3ttAqp9W2wwonUmcb",
1349
+ cid: "QmespREceysQ7WPqGKg1VpHB3hf8zsreMY8HSjiQ2xoAN4",
1243
1350
  authorizedCidHex: cidToHex(
1244
- "QmP2u3bgKLtfqsFMp6Li72zMY8ZZH3ttAqp9W2wwonUmcb"
1351
+ "QmespREceysQ7WPqGKg1VpHB3hf8zsreMY8HSjiQ2xoAN4"
1245
1352
  ),
1246
1353
  name: "Extend Position Validator",
1247
1354
  description: "Production Extend Position Validator",
1248
- version: "1.0.1",
1355
+ version: "1.2.1",
1249
1356
  deployed: true,
1250
- deployedAt: 1781542211640,
1251
- size: 66543,
1252
- hash: "0db666fa2888e9acea13d8e4d79edd4ca252f5a4bb06d97e53aa14a9a34b7143",
1357
+ deployedAt: 1784316534278,
1358
+ size: 74352,
1359
+ hash: "c17bfc9abef16f2372316a14c14281e2acc98afdd1662d095360f1d1c5383f1d",
1253
1360
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1254
1361
  pkp: {
1255
1362
  publicKey: "0x04e52338f6a0c3362800f1a94c4c8f08be5030dde0fa217388c53d1c1a4790f4443c9f2886b2d2d8f4b75a9a156e9b028011b1a1cf7648da0e556aa980ec692836",
@@ -1257,17 +1364,17 @@ var require_pkg_src = __commonJS({
1257
1364
  }
1258
1365
  },
1259
1366
  btcWithdrawal: {
1260
- cid: "QmXtmLdGuEqMKKAtV7d8sHpUV32Kt74na4dRQdpWLasbHJ",
1367
+ cid: "QmYG1o9qMBZsEMDE9DNsJMzQmvxfj6jTu9V37WwuJZAtC2",
1261
1368
  authorizedCidHex: cidToHex(
1262
- "QmXtmLdGuEqMKKAtV7d8sHpUV32Kt74na4dRQdpWLasbHJ"
1369
+ "QmYG1o9qMBZsEMDE9DNsJMzQmvxfj6jTu9V37WwuJZAtC2"
1263
1370
  ),
1264
1371
  name: "Btc Withdrawal",
1265
1372
  description: "Production Btc Withdrawal",
1266
- version: "1.0.0",
1373
+ version: "1.2.1",
1267
1374
  deployed: true,
1268
- deployedAt: 1781542212709,
1269
- size: 79782,
1270
- hash: "e824eb473df8eba3a025ac3adc46730dbf89f90884d7c05a78cc07b90d6bc4df",
1375
+ deployedAt: 1784316549409,
1376
+ size: 90692,
1377
+ hash: "ca7276763142fe1fc812b70f6319973000faf338a91c531f898f8f6406035c64",
1271
1378
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1272
1379
  pkp: {
1273
1380
  publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
@@ -1275,17 +1382,17 @@ var require_pkg_src = __commonJS({
1275
1382
  }
1276
1383
  },
1277
1384
  liquidationValidator: {
1278
- cid: "QmVicBXuZVY8t4DXLPjvXjzLqnBtnogAh5HT7BaL9VeDwF",
1385
+ cid: "QmZJnGq2HEYW1XYZPSodyRCrbDhTcLDT8x4z76EiPg8Xcx",
1279
1386
  authorizedCidHex: cidToHex(
1280
- "QmVicBXuZVY8t4DXLPjvXjzLqnBtnogAh5HT7BaL9VeDwF"
1387
+ "QmZJnGq2HEYW1XYZPSodyRCrbDhTcLDT8x4z76EiPg8Xcx"
1281
1388
  ),
1282
1389
  name: "Liquidation Validator",
1283
1390
  description: "Production Liquidation Validator",
1284
- version: "1.0.0",
1391
+ version: "1.2.1",
1285
1392
  deployed: true,
1286
- deployedAt: 1781542213421,
1287
- size: 58444,
1288
- hash: "0f42e9f8984433fef95875d35eb3c8d985e7ce15ad309e6aa1093d6fd1ba0bb5",
1393
+ deployedAt: 1784316560010,
1394
+ size: 66523,
1395
+ hash: "f74d0163c47d7db04d1547f72c3a9bc1e5b499669089cc6464479a361462ce81",
1289
1396
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1290
1397
  pkp: {
1291
1398
  publicKey: "0x041a241de58976c13e3d7ca5a18e494f7330151e3818706135d50176e78ffb673e58b11f0b67a87c73fade6c6b27d6e7a7377ea9f4c10bf31880f1d725f18cc6f8",
@@ -1311,17 +1418,17 @@ var require_pkg_src = __commonJS({
1311
1418
  }
1312
1419
  },
1313
1420
  loanVaultBtcBalance: {
1314
- cid: "QmUmqL4LcjDSpw2TtuWMcw8xYk8hPxgagY2qHDU8Mx1PLG",
1421
+ cid: "QmVKSkgY8e5X6v6yRD4H2wpU2oX2kbeKYh4fjPepg71mqu",
1315
1422
  authorizedCidHex: cidToHex(
1316
- "QmUmqL4LcjDSpw2TtuWMcw8xYk8hPxgagY2qHDU8Mx1PLG"
1423
+ "QmVKSkgY8e5X6v6yRD4H2wpU2oX2kbeKYh4fjPepg71mqu"
1317
1424
  ),
1318
1425
  name: "Loan Vault Btc Balance",
1319
1426
  description: "Production Loan Vault Btc Balance",
1320
- version: "1.0.0",
1427
+ version: "1.2.1",
1321
1428
  deployed: true,
1322
- deployedAt: 1781542215432,
1323
- size: 48397,
1324
- hash: "da3750a70e532b87f3590a3e2d8c2d51e700589f6e111bb291384ab01304af35",
1429
+ deployedAt: 1784316574669,
1430
+ size: 56477,
1431
+ hash: "917535f2911055b2ade2204fd868dd8b7294c7ade23e82aea488aeb0872e35d7",
1325
1432
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1326
1433
  pkp: {
1327
1434
  publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
@@ -1335,17 +1442,17 @@ var require_pkg_src = __commonJS({
1335
1442
  // Requires BitcoinProviderRegistry.addAllowedCid(chainId, keccak256(utf8(cid)))
1336
1443
  // on each target chain (the action decrypts registry slots). No OAR entry.
1337
1444
  btcAddressBalance: {
1338
- cid: "QmWRDAg7ZCBFFhGfdXR7aUtQsG5qqBb1yVdLCxh8dPD7G5",
1445
+ cid: "QmcYFLDbhQArk5gQK44Xj8CnYEAbnaNsei92sUHpRuFmZA",
1339
1446
  authorizedCidHex: cidToHex(
1340
- "QmWRDAg7ZCBFFhGfdXR7aUtQsG5qqBb1yVdLCxh8dPD7G5"
1447
+ "QmcYFLDbhQArk5gQK44Xj8CnYEAbnaNsei92sUHpRuFmZA"
1341
1448
  ),
1342
1449
  name: "Btc Address Balance",
1343
1450
  description: "Address-only signed total BTC balance via registry consensus (no positionId, no price)",
1344
- version: "0.1.0",
1451
+ version: "0.2.1",
1345
1452
  deployed: true,
1346
- deployedAt: 1781542217452,
1347
- size: 25519,
1348
- hash: "000e2f8bdbccdc95b0db0fb3f12412e2e76eea16a8456d37914d324437ae291c",
1453
+ deployedAt: 1784316590403,
1454
+ size: 26986,
1455
+ hash: "49fb14265f3208916f12806df04492900bd08fb1b14d9651ee6278cd9ae4a428",
1349
1456
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1350
1457
  pkp: {
1351
1458
  publicKey: "0x0498cbb31facfa26e3ec83ccdabfe4fe341ca0282e0c3cb81da7113723b9726e1e49efa92165cc3faa66ced4454cea56dbab12267852d6525a55d7c4f128b912cd",
@@ -1379,17 +1486,17 @@ var require_pkg_src = __commonJS({
1379
1486
  hash: "03ee5be239c91c735d2c95db89a0d1b77e602cfc18af8eb64b3281fddcd90a72"
1380
1487
  },
1381
1488
  staleSpendRecoveryValidator: {
1382
- cid: "QmWqJZqoKUqMaGTXe9WUSwdDyeBwXJ9dnR3ioFnVK4RkN5",
1489
+ cid: "QmcLrhiM1JwVi5w3QFxBYi1V4DaFT3Kk9MhPywyMnTsEwK",
1383
1490
  authorizedCidHex: cidToHex(
1384
- "QmWqJZqoKUqMaGTXe9WUSwdDyeBwXJ9dnR3ioFnVK4RkN5"
1491
+ "QmcLrhiM1JwVi5w3QFxBYi1V4DaFT3Kk9MhPywyMnTsEwK"
1385
1492
  ),
1386
1493
  name: "Stale Spend Recovery Validator",
1387
1494
  description: "Production Stale Spend Recovery Validator",
1388
- version: "1.0.0",
1495
+ version: "1.1.2",
1389
1496
  deployed: true,
1390
- deployedAt: 1781542219438,
1391
- size: 34413,
1392
- hash: "26b11730cf5d9eb4af0b1fe65192d26ccee50369c0472211a0ea11e26ef51cc5",
1497
+ deployedAt: 1784316604502,
1498
+ size: 36785,
1499
+ hash: "28bfe44970541fefe3bae8bd54f25e8775d7cc61b5832a40ba1c18587b6b6358",
1393
1500
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1394
1501
  pkp: {
1395
1502
  publicKey: "0x046da0ad6da7dd4a0063cbeac6f2bdb6a9889c2178b23470833356a8ee5630f7f0eee0db348d9d9aaaadd23eccf0063c8f68ee5e446abe30b882bf1de4c932bc21",
@@ -2140,7 +2247,7 @@ ${errorReport}`);
2140
2247
  }
2141
2248
  init_debug_logger();
2142
2249
  init_session_signature_cache();
2143
- var import_ethers15 = require("ethers");
2250
+ var import_ethers16 = require("ethers");
2144
2251
  var EXPIRED_LOAN_MIN_LIQUIDATION_THRESHOLD_BPS = 11e3;
2145
2252
  var GRACE_PERIOD_DAYS = 30;
2146
2253
  var SATOSHIS_PER_BITCOIN = 100000000n;
@@ -2177,6 +2284,20 @@ ${errorReport}`);
2177
2284
  /* REGTEST */
2178
2285
  // Local development
2179
2286
  };
2287
+ var CHAIN_TO_CHAIN_ID = {
2288
+ [
2289
+ "ethereum"
2290
+ /* ETHEREUM */
2291
+ ]: 1,
2292
+ [
2293
+ "sepolia"
2294
+ /* SEPOLIA */
2295
+ ]: 11155111,
2296
+ [
2297
+ "hardhat"
2298
+ /* HARDHAT */
2299
+ ]: 1337
2300
+ };
2180
2301
  function validateChain(chain) {
2181
2302
  const normalized = chain.toLowerCase().trim();
2182
2303
  if (normalized === "sepolia") {
@@ -2473,6 +2594,27 @@ ${errorReport}`);
2473
2594
  function createProtocolParametersModule(config) {
2474
2595
  return new ProtocolParametersModule(config);
2475
2596
  }
2597
+ function parseVaultAddress(rawVaultAddress, chain) {
2598
+ try {
2599
+ const parsed = JSON.parse(rawVaultAddress);
2600
+ if (typeof parsed === "object" && parsed !== null) {
2601
+ const chainLower = chain.toLowerCase();
2602
+ if (chainLower === "sepolia") {
2603
+ return parsed.regtest || parsed.testnet || parsed.mainnet;
2604
+ }
2605
+ const bitcoinNetwork = getBitcoinNetworkForChain(chain);
2606
+ if (bitcoinNetwork === "regtest") {
2607
+ return parsed.regtest || parsed.testnet || parsed.mainnet;
2608
+ }
2609
+ if (bitcoinNetwork === "testnet") {
2610
+ return parsed.testnet || parsed.regtest || parsed.mainnet;
2611
+ }
2612
+ return parsed.mainnet;
2613
+ }
2614
+ } catch (e) {
2615
+ }
2616
+ return rawVaultAddress;
2617
+ }
2476
2618
  var VaultSnapshotModule = class {
2477
2619
  constructor(config) {
2478
2620
  this.config = config;
@@ -2580,112 +2722,11 @@ ${errorReport}`);
2580
2722
  timestamp: Date.now()
2581
2723
  };
2582
2724
  }
2583
- /**
2584
- * Fast vault snapshot using balance method only (no UTXO validation)
2585
- * This is optimized for speed and should be used when UTXO details are not needed.
2586
- *
2587
- * WARNING: This method skips UTXO validation. Use ONLY for non-critical operations.
2588
- * For authorization/security operations, ALWAYS use getVaultSnapshot().
2589
- *
2590
- * @param positionId - Position to snapshot
2591
- * @returns Complete VaultSnapshot with all metrics (using fast balance method)
2592
- */
2593
- async getVaultSnapshotFast(positionId) {
2594
- const positionState = await this.queryPositionState(positionId);
2595
- console.log(
2596
- `[Vault Snapshot] Raw vault address from contract: "${positionState.vaultAddress}"`
2597
- );
2598
- const useStubbedBtcData = this.config.debugOverrides?.useStubbedBtcData === true;
2599
- const [balanceResult, btcPriceUsd] = useStubbedBtcData ? this.buildStubbedBtcSnapshotInputs(positionState) : await Promise.all([
2600
- this.config.vaultBalance.calculateTrustedBalance(
2601
- positionId,
2602
- positionState.vaultAddress,
2603
- true
2604
- // Always skip UTXO validation for fast path
2605
- ),
2606
- this.config.priceOracle.getBTCPrice()
2607
- ]);
2608
- const protocolParams = createProtocolParametersModule({
2609
- termManagerAddress: this.config.termManagerAddress,
2610
- loanOpsManagerAddress: this.config.loanOpsManagerAddress,
2611
- liquidationManagerAddress: this.config.liquidationManagerAddress,
2612
- chain: this.config.chain,
2613
- chainId: this.config.chainId || 1,
2614
- // Default to mainnet if not provided
2615
- rpcUrl: this.config.rpcUrl
2616
- });
2617
- const termForFees = this.config.termOverride ?? positionState.selectedTerm;
2618
- const [liquidationThresholdBps, maxEscalatedThresholdBps, termFees] = await Promise.all([
2619
- protocolParams.getLiquidationThreshold(),
2620
- protocolParams.getMaxEscalatedThreshold(),
2621
- protocolParams.getTermFees(termForFees)
2622
- ]);
2623
- const termStatus = calculateTermStatus(
2624
- positionState.termStartTimestamp,
2625
- positionState.selectedTerm
2626
- );
2627
- const currentLiquidationThreshold = calculateLiquidationThreshold(
2628
- termStatus.isExpired,
2629
- termStatus.daysIntoGracePeriod,
2630
- liquidationThresholdBps,
2631
- maxEscalatedThresholdBps
2632
- );
2633
- const collateralMetrics = calculateCollateralMetrics(
2634
- balanceResult.availableBalance,
2635
- btcPriceUsd,
2636
- positionState.ucdDebt
2637
- );
2638
- const isLiquidatable = collateralMetrics.collateralRatioBps < currentLiquidationThreshold;
2639
- const marginToLiquidationBps = collateralMetrics.collateralRatioBps - currentLiquidationThreshold;
2640
- return {
2641
- // Raw state
2642
- positionId: positionState.positionId,
2643
- pkpId: positionState.pkpId,
2644
- borrower: positionState.borrower,
2645
- vaultAddress: positionState.vaultAddress,
2646
- ucdDebt: positionState.ucdDebt,
2647
- termStartTimestamp: positionState.termStartTimestamp,
2648
- selectedTerm: positionState.selectedTerm,
2649
- status: positionState.status,
2650
- // Loan status enum value (0-7)
2651
- expiryAt: positionState.expiryAt,
2652
- // Loan expiry timestamp
2653
- previousExpiryAt: positionState.previousExpiryAt,
2654
- // expiryAt before last renewal; 0 if never renewed
2655
- totalTerm: positionState.totalTerm,
2656
- // total accumulated loan months; 0 before first renewal
2657
- lastRenewedAt: positionState.lastRenewedAt,
2658
- // timestamp of most recent renewal; createdAt at origination; 0 for pre-H-5 positions
2659
- // Bitcoin state
2660
- totalBTCSats: balanceResult.totalBalance,
2661
- totalUTXOs: balanceResult.totalUTXOs,
2662
- authorizedSpendsSats: balanceResult.authorizedBalance,
2663
- authorizedSpendsHash: balanceResult.authorizedSpendsHash,
2664
- availableBTCSats: balanceResult.availableBalance,
2665
- availableUTXOs: balanceResult.availableUTXOs,
2666
- // Price data
2667
- btcPriceUsd,
2668
- // Calculated metrics
2669
- collateralValueUsd: collateralMetrics.collateralValueUsd,
2670
- collateralRatioBps: collateralMetrics.collateralRatioBps,
2671
- // Term status
2672
- termDurationDays: termStatus.termDurationDays,
2673
- termLengthDays: termStatus.termLengthDays,
2674
- isExpired: termStatus.isExpired,
2675
- daysUntilExpiry: termStatus.daysUntilExpiry,
2676
- daysIntoGracePeriod: termStatus.daysIntoGracePeriod,
2677
- // Liquidation risk
2678
- currentLiquidationThreshold,
2679
- isLiquidatable,
2680
- marginToLiquidationBps,
2681
- // Protocol parameters (from smart contracts)
2682
- liquidationThresholdBps,
2683
- originationFeeBps: termFees.originationFeeBps,
2684
- extensionFeeBps: termFees.extensionFeeBps,
2685
- // Metadata
2686
- timestamp: Date.now()
2687
- };
2688
- }
2725
+ // NOTE (remediation P1/#5): `getVaultSnapshotFast` — a snapshot built on the
2726
+ // skip-UTXO-validation balance path — was deleted. It had zero callers, and
2727
+ // a snapshot whose balance skipped UTXO validation + consensus must never
2728
+ // exist within reach of a signing flow. Every snapshot now runs the full
2729
+ // validation path above.
2689
2730
  buildStubbedBtcSnapshotInputs(positionState) {
2690
2731
  const stubbedAvailableBtcSats = BigInt(
2691
2732
  this.config.debugOverrides?.stubbedAvailableBtcSats ?? "10000000"
@@ -2814,14 +2855,20 @@ ${errorReport}`);
2814
2855
  expiryAt,
2815
2856
  selectedTermMonths
2816
2857
  );
2858
+ const parsedVaultAddress = parseVaultAddress(
2859
+ position.vaultAddress,
2860
+ validateChain(this.config.chain)
2861
+ );
2862
+ if (typeof parsedVaultAddress !== "string" || parsedVaultAddress.length === 0) {
2863
+ throw new Error(
2864
+ `Position ${positionIdBytes32} vaultAddress has no entry for chain "${this.config.chain}"`
2865
+ );
2866
+ }
2817
2867
  return {
2818
2868
  positionId: position.positionId,
2819
2869
  pkpId: position.pkpId,
2820
2870
  borrower: position.borrower,
2821
- vaultAddress: this.parseVaultAddress(
2822
- position.vaultAddress,
2823
- validateChain(this.config.chain)
2824
- ),
2871
+ vaultAddress: parsedVaultAddress,
2825
2872
  ucdDebt: BigInt(position.ucdDebt.toString()),
2826
2873
  termStartTimestamp,
2827
2874
  selectedTerm: selectedTermMonths,
@@ -2832,36 +2879,8 @@ ${errorReport}`);
2832
2879
  lastRenewedAt
2833
2880
  };
2834
2881
  }
2835
- /**
2836
- * Parse vault address based on format and network
2837
- *
2838
- * Supports two formats:
2839
- * 1. Single string: "mqM3ZR2N6DzZkafSdFqxEAkWovAU3uYTAN"
2840
- * 2. JSON object: {"mainnet":"bc1q...","testnet":"mqM3ZR2N6DzZkafSdFqxEAkWovAU3uYTAN"}
2841
- *
2842
- * @param rawVaultAddress Raw vault address from contract
2843
- * @param chain EVM chain (sepolia, ethereum)
2844
- * @returns Parsed Bitcoin address for the appropriate network
2845
- */
2846
- parseVaultAddress(rawVaultAddress, chain) {
2847
- try {
2848
- const parsed = JSON.parse(rawVaultAddress);
2849
- if (typeof parsed === "object" && parsed !== null) {
2850
- const chainLower = chain.toLowerCase();
2851
- if (chainLower === "sepolia") {
2852
- return parsed.regtest || parsed.testnet || parsed.mainnet;
2853
- }
2854
- const bitcoinNetwork = getBitcoinNetworkForChain(chain);
2855
- if (bitcoinNetwork === "testnet") {
2856
- return parsed.testnet || parsed.regtest || parsed.mainnet;
2857
- } else {
2858
- return parsed.mainnet;
2859
- }
2860
- }
2861
- } catch (e) {
2862
- }
2863
- return rawVaultAddress;
2864
- }
2882
+ // parseVaultAddress moved to `modules/vault-address.ts` (shared with the
2883
+ // phase-2 signer for the P6/#7b vault-identity binding).
2865
2884
  /**
2866
2885
  * Quick check if position is liquidatable
2867
2886
  *
@@ -3199,6 +3218,13 @@ ${auth.clientId}`;
3199
3218
  return {};
3200
3219
  }
3201
3220
  }
3221
+ function assertSaneSatoshis(value, context) {
3222
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_BTC_AMOUNT) {
3223
+ throw new Error(
3224
+ `${context}: satoshi value ${value} is not a sane amount (must be an integer in [0, ${MAX_BTC_AMOUNT}])`
3225
+ );
3226
+ }
3227
+ }
3202
3228
  var BitcoinDataProvider = class {
3203
3229
  constructor(config) {
3204
3230
  this.config = config;
@@ -3345,13 +3371,16 @@ ${auth.clientId}`;
3345
3371
  address
3346
3372
  );
3347
3373
  const utxos = unspentOutputs.map(
3348
- (utxo) => ({
3349
- txid: utxo.txid,
3350
- vout: utxo.vout,
3351
- satoshis: BigInt(Math.round(utxo.amount * 1e8)),
3352
- // BTC to satoshis as bigint
3353
- confirmations: utxo.confirmations
3354
- })
3374
+ (utxo) => {
3375
+ const satNumber = Math.round(utxo.amount * 1e8);
3376
+ assertSaneSatoshis(satNumber, `fetchUTXOsFromRPC ${utxo.txid}:${utxo.vout}`);
3377
+ return {
3378
+ txid: utxo.txid,
3379
+ vout: utxo.vout,
3380
+ satoshis: BigInt(satNumber),
3381
+ confirmations: utxo.confirmations
3382
+ };
3383
+ }
3355
3384
  );
3356
3385
  return utxos;
3357
3386
  }
@@ -3393,6 +3422,7 @@ ${auth.clientId}`;
3393
3422
  "Invalid UTXO row: missing txid, vout/n, or value/satoshis"
3394
3423
  );
3395
3424
  }
3425
+ assertSaneSatoshis(satRaw, `parseBlockstreamUTXOs ${txid}:${vout}`);
3396
3426
  utxos.push({
3397
3427
  txid,
3398
3428
  vout,
@@ -3571,7 +3601,10 @@ ${auth.clientId}`;
3571
3601
  `Invalid response: missing required fields. funded_txo_sum: ${fundedTxoSum}, spent_txo_sum: ${spentTxoSum}`
3572
3602
  );
3573
3603
  }
3604
+ assertSaneSatoshis(fundedTxoSum, `getBalance ${cleanAddress} funded_txo_sum`);
3605
+ assertSaneSatoshis(spentTxoSum, `getBalance ${cleanAddress} spent_txo_sum`);
3574
3606
  const confirmedBalance = fundedTxoSum - spentTxoSum;
3607
+ assertSaneSatoshis(confirmedBalance, `getBalance ${cleanAddress} confirmed balance`);
3575
3608
  const balance = BigInt(confirmedBalance);
3576
3609
  this.logger.log("3", `Balance: ${balance.toString()} sats`);
3577
3610
  this.logger.log(
@@ -4050,20 +4083,590 @@ ${auth.clientId}`;
4050
4083
  return priceWith8Decimals;
4051
4084
  }
4052
4085
  };
4086
+ var MAX_CLASSIFIED_PARENTS = 5;
4087
+ var CLASSIFICATION_CONCURRENCY = 2;
4088
+ var CONFIRMATION_SKEW_TOLERANCE = 3;
4089
+ async function mapWithConcurrency(items, limit, fn) {
4090
+ const results = new Array(items.length);
4091
+ let next = 0;
4092
+ const workers = Array.from(
4093
+ { length: Math.min(limit, items.length) },
4094
+ async () => {
4095
+ while (true) {
4096
+ const i = next++;
4097
+ if (i >= items.length) {
4098
+ return;
4099
+ }
4100
+ results[i] = await fn(items[i]);
4101
+ }
4102
+ }
4103
+ );
4104
+ await Promise.all(workers);
4105
+ return results;
4106
+ }
4107
+ function findOutputClaimMismatch(claimedUtxos, outputs, cleanVaultAddress) {
4108
+ for (const claim of claimedUtxos) {
4109
+ const output = outputs.find((o) => o.index === claim.vout);
4110
+ if (!output) {
4111
+ return `claimed output ${claim.txid}:${claim.vout} not present in parent tx`;
4112
+ }
4113
+ if (output.scriptpubkey_address !== cleanVaultAddress) {
4114
+ return `claimed output ${claim.txid}:${claim.vout} pays ${output.scriptpubkey_address ?? "<no address>"}, not the vault`;
4115
+ }
4116
+ if (output.value === void 0 || output.value !== claim.satoshis) {
4117
+ return `claimed output ${claim.txid}:${claim.vout} value ${output.value?.toString() ?? "<missing>"} != reported ${claim.satoshis.toString()}`;
4118
+ }
4119
+ }
4120
+ return null;
4121
+ }
4122
+ function stripNetworkPrefix(address) {
4123
+ const prefixPattern = /^(REGTEST_|TESTNET_|MAINNET_)/;
4124
+ return address.replace(prefixPattern, "");
4125
+ }
4126
+ var VaultBalanceConsensus = class {
4127
+ /**
4128
+ * @param minConsensusProviders Policy floor (`policy.minConsensusProviders`,
4129
+ * remediation P1/#1). Both consensus passes THROW when fewer providers are
4130
+ * configured than the floor demands — below it the cross-checks cannot run
4131
+ * and neither trusting the primary (inflation) nor excluding UTXOs
4132
+ * (deflation → wrongful liquidation) is a safe direction. A floor of 1
4133
+ * (hardhat / single-provider sepolia) preserves the trust-primary /
4134
+ * exclude-unconfirmed baseline unchanged.
4135
+ */
4136
+ constructor(primaryProvider, providerConfigs, minConsensusProviders) {
4137
+ this.primaryProvider = primaryProvider;
4138
+ this.providerConfigs = providerConfigs;
4139
+ this.minConsensusProviders = minConsensusProviders;
4140
+ this.readers = this.buildReaders();
4141
+ }
4142
+ /**
4143
+ * Fail-closed floor check shared by both consensus passes. Only floors above
4144
+ * 1 can bite: a floor of 1 with zero configs means "no esplora access
4145
+ * configured" (dev/test), where the documented pre-gate behaviour —
4146
+ * trust-primary confirmed set, exclude unconfirmed — remains correct.
4147
+ */
4148
+ assertProviderFloor(pass) {
4149
+ const configured = this.providerConfigs?.length ?? 0;
4150
+ if (this.minConsensusProviders > 1 && configured < this.minConsensusProviders) {
4151
+ throw new Error(
4152
+ `${pass}: this chain requires at least ${this.minConsensusProviders} Bitcoin providers for consensus; ${configured} configured. Refusing to proceed without cross-check.`
4153
+ );
4154
+ }
4155
+ }
4156
+ /**
4157
+ * Build one data reader per configured provider (primary first). Index 0
4158
+ * reuses the injected primary instance; the rest are constructed from their
4159
+ * configs so cross-check reads hit the right endpoint. Null when no providers
4160
+ * are configured — both passes then fall back to primary-only behaviour.
4161
+ */
4162
+ buildReaders() {
4163
+ const configs = this.providerConfigs;
4164
+ if (!configs || configs.length === 0) {
4165
+ return null;
4166
+ }
4167
+ return configs.map(
4168
+ (cfg, i) => i === 0 ? this.primaryProvider : new BitcoinDataProvider({
4169
+ providerUrl: cfg.providerUrl,
4170
+ auth: cfg.auth,
4171
+ ...cfg.timeout ? { timeout: cfg.timeout } : {}
4172
+ })
4173
+ );
4174
+ }
4175
+ /**
4176
+ * Confirmed-UTXO-set consensus across every registered provider (strict).
4177
+ *
4178
+ * The signed balance is dominated by the primary's confirmed UTXOs, but the
4179
+ * UTXO set — txid, vout, and value — is read only from the primary; the
4180
+ * `assertBitcoinBalanceConsensus` check the validators run compares
4181
+ * `chain_stats` totals, not individual outpoints. So a compromised primary
4182
+ * can add a fabricated confirmed UTXO, or inflate a real one's value, and the
4183
+ * balance consensus still passes (it serves honest `chain_stats`). This
4184
+ * closes that: each UTXO the primary reports at or above `minConfirmations`
4185
+ * must be present with the EXACT same value on every registered provider.
4186
+ *
4187
+ * Semantics:
4188
+ * - Absent on any provider, or value mismatch → THROW (fail-closed). Confirmed
4189
+ * transactions are fully propagated, so absence is unambiguous forgery, not
4190
+ * a race.
4191
+ * - Present + value-matched everywhere: the UTXO counts as confirmed only if
4192
+ * it meets `minConfirmations` on ALL providers (gated on the minimum
4193
+ * reported depth). A UTXO just past the boundary on the primary but a block
4194
+ * behind on a lagging provider is DEMOTED — excluded until it matures —
4195
+ * rather than signed over. A depth spread beyond CONFIRMATION_SKEW_TOLERANCE
4196
+ * is not benign skew but a provider lying about depth (to inflate a shallow
4197
+ * UTXO past the gate, or deflate a deep one into a wrongful liquidation) →
4198
+ * THROW.
4199
+ * - SYMMETRIC (finding #6): a confirmed outpoint any secondary reports that
4200
+ * the primary omits entirely, reports with a different value, or reports
4201
+ * at a depth more than the skew tolerance BELOW the secondary's → THROW.
4202
+ * Without this, a compromised primary deflates the balance by omission and
4203
+ * forces a wrongful liquidation.
4204
+ *
4205
+ * Below `minConsensusProviders` this THROWS (P1/#1). With a floor of 1 and
4206
+ * 0/1 providers configured, no cross-check is possible and the primary's
4207
+ * confirmed UTXOs are returned unchanged (behaviour identical to before
4208
+ * this gate).
4209
+ *
4210
+ * Cross-checking `chain_stats` totals against the confirmed-UTXO sum was
4211
+ * considered and rejected: `getBalance` reads mempool-blind `chain_stats`
4212
+ * while `getUTXOSet` removes mempool-spent outputs, so the two legitimately
4213
+ * diverge during any pending spend — precisely the self-change window — and
4214
+ * that comparison would false-fail there. Outpoint-set consensus asserts
4215
+ * agreement only on confirmed outpoints, which the mempool does not touch.
4216
+ *
4217
+ * @returns Set of "txid:vout" keys admitted as confirmed-with-consensus.
4218
+ */
4219
+ async confirmedUtxoConsensus(primaryUtxos, vaultAddress, minConfirmations) {
4220
+ const providerConfigs = this.providerConfigs;
4221
+ const readers = this.readers;
4222
+ this.assertProviderFloor("confirmedUtxoConsensus");
4223
+ const confirmedKeys = /* @__PURE__ */ new Set();
4224
+ const primaryConfirmed = primaryUtxos.filter(
4225
+ (utxo) => utxo.confirmations >= minConfirmations
4226
+ );
4227
+ for (const utxo of primaryConfirmed) {
4228
+ confirmedKeys.add(`${utxo.txid}:${utxo.vout}`);
4229
+ }
4230
+ if (!providerConfigs || providerConfigs.length <= 1 || !readers) {
4231
+ return confirmedKeys;
4232
+ }
4233
+ const providerName = (i) => providerConfigs[i].name || providerConfigs[i].providerUrl;
4234
+ const settled = await Promise.allSettled(
4235
+ readers.slice(1).map((reader) => reader.getUTXOSet(vaultAddress, minConfirmations))
4236
+ );
4237
+ const failures = settled.map((r, i) => ({ r, name: providerName(i + 1) })).filter(
4238
+ (x) => x.r.status === "rejected"
4239
+ ).map(
4240
+ ({ r, name }) => `${name} \u2192 ${r.reason?.message ?? String(r.reason)}`
4241
+ );
4242
+ if (failures.length > 0) {
4243
+ throw new Error(
4244
+ `Confirmed UTXO set read failed for ${vaultAddress} on ${failures.length}/${providerConfigs.length - 1} secondary provider(s): ${failures.join("; ")}`
4245
+ );
4246
+ }
4247
+ const secondaryMaps = settled.map((r) => {
4248
+ const map2 = /* @__PURE__ */ new Map();
4249
+ for (const utxo of r.value.utxos) {
4250
+ map2.set(`${utxo.txid}:${utxo.vout}`, {
4251
+ value: utxo.satoshis,
4252
+ confirmations: utxo.confirmations
4253
+ });
4254
+ }
4255
+ return map2;
4256
+ });
4257
+ const demoted = [];
4258
+ for (const utxo of primaryConfirmed) {
4259
+ const key = `${utxo.txid}:${utxo.vout}`;
4260
+ let minConfs = utxo.confirmations;
4261
+ let maxConfs = utxo.confirmations;
4262
+ for (let s = 0; s < secondaryMaps.length; s++) {
4263
+ const entry = secondaryMaps[s].get(key);
4264
+ const name = providerName(s + 1);
4265
+ if (!entry) {
4266
+ throw new Error(
4267
+ `Confirmed UTXO ${key} present on ${providerName(0)} but absent on ${name}; refusing to sign (possible fabricated UTXO).`
4268
+ );
4269
+ }
4270
+ if (entry.value !== utxo.satoshis) {
4271
+ throw new Error(
4272
+ `Confirmed UTXO ${key} value mismatch: ${providerName(0)}=${utxo.satoshis.toString()} vs ${name}=${entry.value.toString()}; refusing to sign.`
4273
+ );
4274
+ }
4275
+ if (entry.confirmations < minConfs)
4276
+ minConfs = entry.confirmations;
4277
+ if (entry.confirmations > maxConfs)
4278
+ maxConfs = entry.confirmations;
4279
+ }
4280
+ if (maxConfs - minConfs > CONFIRMATION_SKEW_TOLERANCE) {
4281
+ throw new Error(
4282
+ `Confirmed UTXO ${key} confirmation spread ${maxConfs - minConfs} exceeds tolerance ${CONFIRMATION_SKEW_TOLERANCE} (min ${minConfs}, max ${maxConfs}); providers disagree on depth, refusing to sign.`
4283
+ );
4284
+ }
4285
+ if (minConfs < minConfirmations) {
4286
+ confirmedKeys.delete(key);
4287
+ demoted.push(key);
4288
+ }
4289
+ }
4290
+ if (demoted.length > 0) {
4291
+ litDebugLog(
4292
+ `[VaultBalance] confirmed-consensus: ${demoted.length} UTXO(s) demoted pending provider tip skew (excluded until matured): ${demoted.join(", ")}`
4293
+ );
4294
+ }
4295
+ const primaryByKey = new Map(
4296
+ primaryUtxos.map((utxo) => [`${utxo.txid}:${utxo.vout}`, utxo])
4297
+ );
4298
+ for (let s = 0; s < secondaryMaps.length; s++) {
4299
+ const name = providerName(s + 1);
4300
+ for (const [key, entry] of secondaryMaps[s]) {
4301
+ if (entry.confirmations < minConfirmations)
4302
+ continue;
4303
+ const primaryUtxo = primaryByKey.get(key);
4304
+ if (!primaryUtxo) {
4305
+ throw new Error(
4306
+ `Confirmed UTXO ${key} present on ${name} (${entry.confirmations} confs) but absent on ${providerName(0)}; refusing to sign (possible omitted UTXO / balance deflation \u2014 may be transient during an in-flight vault spend).`
4307
+ );
4308
+ }
4309
+ if (primaryUtxo.satoshis !== entry.value) {
4310
+ throw new Error(
4311
+ `Confirmed UTXO ${key} value mismatch: ${providerName(0)}=${primaryUtxo.satoshis.toString()} vs ${name}=${entry.value.toString()}; refusing to sign.`
4312
+ );
4313
+ }
4314
+ if (entry.confirmations - primaryUtxo.confirmations > CONFIRMATION_SKEW_TOLERANCE) {
4315
+ throw new Error(
4316
+ `Confirmed UTXO ${key} depth disagreement: ${providerName(0)}=${primaryUtxo.confirmations} vs ${name}=${entry.confirmations} exceeds tolerance ${CONFIRMATION_SKEW_TOLERANCE}; the primary may be under-reporting depth to suppress collateral, refusing to sign.`
4317
+ );
4318
+ }
4319
+ }
4320
+ }
4321
+ return confirmedKeys;
4322
+ }
4323
+ /**
4324
+ * Classify unconfirmed UTXOs as protocol self-change.
4325
+ *
4326
+ * A UTXO below minConfirmations counts as confirmed-equivalent ONLY when the
4327
+ * transaction that created it spends exclusively vault-owned, confirmed
4328
+ * outputs. Such a transaction can only have been signed by the vault's PKP,
4329
+ * so no third party can RBF-replace or double-spend it — the reversal risk
4330
+ * the confirmation gate exists to close does not apply. Any non-vault input,
4331
+ * unconfirmed funding tx, or coinbase input disqualifies the UTXO
4332
+ * (fail-safe: exclusion, never inclusion).
4333
+ *
4334
+ * Multi-provider consensus (audit C-1/C-2 parity): the parent-tx proof is
4335
+ * verified independently against EVERY configured provider and admitted
4336
+ * only on unanimity. The confirmed-balance consensus the validators run
4337
+ * covers `chain_stats` only, so without this the primary provider alone
4338
+ * could fabricate an unconfirmed UTXO plus a self-consistent parent proof
4339
+ * and inflate the signed balance. Any provider failure or verdict
4340
+ * disagreement THROWS (fail-closed) — exclusion on a suppressed proof
4341
+ * would deflate the balance and open a wrongful-liquidation window, so
4342
+ * neither direction may proceed without agreement.
4343
+ *
4344
+ * The same per-provider `/tx` fetch also returns the parent's outputs, and
4345
+ * every provider must corroborate each claimed UTXO (index, vault address,
4346
+ * exact value) before admission — the UTXO list itself comes only from the
4347
+ * primary, so an unchecked claim would let it attach a fabricated or
4348
+ * inflated output to a genuine self-spend.
4349
+ *
4350
+ * Fetch cost: zero when every UTXO is confirmed (the common case); otherwise
4351
+ * per provider: one tx lookup per classified parent (vins + vouts in one
4352
+ * response), plus per distinct funding tx a confirmation check and — only
4353
+ * when the provider omits vin prevout data — an output lookup. Classified
4354
+ * parents are capped at MAX_CLASSIFIED_PARENTS (largest claimed value
4355
+ * first, deterministic) and evaluated CLASSIFICATION_CONCURRENCY at a time,
4356
+ * so third-party dust spam cannot amplify into provider rate-limiting and
4357
+ * an action-wide outage.
4358
+ *
4359
+ * See docs/vault-balance-confirmed-gate-plan-2026-07-14.md §3.1.
4360
+ *
4361
+ * @param utxos - Full UTXO set for the vault address
4362
+ * @param vaultAddress - Bitcoin address of the vault (may carry network prefix)
4363
+ * @param minConfirmations - Confirmation threshold for the chain
4364
+ * @returns Set of "txid:vout" keys for UTXOs verified as self-change
4365
+ */
4366
+ async classifySelfChange(utxos, vaultAddress, minConfirmations) {
4367
+ const providerConfigs = this.providerConfigs;
4368
+ const readers = this.readers;
4369
+ const selfChange = /* @__PURE__ */ new Set();
4370
+ const unconfirmed = utxos.filter(
4371
+ (utxo) => utxo.confirmations < minConfirmations
4372
+ );
4373
+ if (unconfirmed.length === 0) {
4374
+ return selfChange;
4375
+ }
4376
+ this.assertProviderFloor("classifySelfChange");
4377
+ if (!providerConfigs || providerConfigs.length === 0 || !readers) {
4378
+ litDebugLog(
4379
+ `[VaultBalance] ${unconfirmed.length} unconfirmed UTXO(s) excluded: no bitcoinProviderConfigs for self-change classification`
4380
+ );
4381
+ return selfChange;
4382
+ }
4383
+ const cleanVaultAddress = stripNetworkPrefix(vaultAddress);
4384
+ const claimsByParent = /* @__PURE__ */ new Map();
4385
+ for (const utxo of unconfirmed) {
4386
+ const claims = claimsByParent.get(utxo.txid) ?? [];
4387
+ claims.push(utxo);
4388
+ claimsByParent.set(utxo.txid, claims);
4389
+ }
4390
+ const claimedSum = (txid) => claimsByParent.get(txid).reduce((sum, utxo) => sum + utxo.satoshis, 0n);
4391
+ const rankedParents = [...claimsByParent.keys()].sort((a, b) => {
4392
+ const diff = claimedSum(b) - claimedSum(a);
4393
+ if (diff !== 0n) {
4394
+ return diff > 0n ? 1 : -1;
4395
+ }
4396
+ return a < b ? -1 : a > b ? 1 : 0;
4397
+ });
4398
+ const parentTxids = rankedParents.slice(0, MAX_CLASSIFIED_PARENTS);
4399
+ const skippedParents = rankedParents.length - parentTxids.length;
4400
+ if (skippedParents > 0) {
4401
+ litDebugLog(
4402
+ `[VaultBalance] self-change: ${skippedParents} parent tx(s) beyond the ${MAX_CLASSIFIED_PARENTS}-parent classification budget excluded (mempool-spam bound; largest claimed value classified first)`
4403
+ );
4404
+ }
4405
+ const verdicts = await mapWithConcurrency(
4406
+ parentTxids,
4407
+ CLASSIFICATION_CONCURRENCY,
4408
+ async (parentTxid) => ({
4409
+ parentTxid,
4410
+ verified: await this.verifyParentIsSelfSpendConsensus(
4411
+ parentTxid,
4412
+ claimsByParent.get(parentTxid),
4413
+ cleanVaultAddress,
4414
+ minConfirmations,
4415
+ providerConfigs,
4416
+ readers
4417
+ )
4418
+ })
4419
+ );
4420
+ const verifiedParents = new Set(
4421
+ verdicts.filter((v) => v.verified).map((v) => v.parentTxid)
4422
+ );
4423
+ for (const utxo of unconfirmed) {
4424
+ if (verifiedParents.has(utxo.txid)) {
4425
+ selfChange.add(`${utxo.txid}:${utxo.vout}`);
4426
+ }
4427
+ }
4428
+ if (verifiedParents.size > 0) {
4429
+ litDebugLog(
4430
+ `[VaultBalance] self-change: ${selfChange.size}/${unconfirmed.length} unconfirmed UTXO(s) verified as protocol change by ${providerConfigs.length} provider(s)`
4431
+ );
4432
+ }
4433
+ return selfChange;
4434
+ }
4435
+ /**
4436
+ * Run the self-spend proof against every configured provider and require
4437
+ * unanimity. Fail-closed: a provider request failure or a verdict
4438
+ * disagreement throws and aborts the balance calculation — see
4439
+ * classifySelfChange for why exclusion is not a safe fallback here.
4440
+ *
4441
+ * When the parent IS a unanimous self-spend, every provider's view of the
4442
+ * parent's OUTPUTS must additionally corroborate the primary's claimed
4443
+ * UTXOs (index exists, pays the vault, exact value). Without this, a
4444
+ * compromised primary could attach a fabricated or value-inflated output
4445
+ * to a genuine self-spend the honest providers happily verify.
4446
+ *
4447
+ * @returns True when every provider verified the parent and corroborated
4448
+ * the claimed outputs, false when every provider rejected the
4449
+ * parent (genuine third-party deposit)
4450
+ */
4451
+ async verifyParentIsSelfSpendConsensus(parentTxid, claimedUtxos, cleanVaultAddress, minConfirmations, providerConfigs, readers) {
4452
+ const providerName = (i) => providerConfigs[i].name || providerConfigs[i].providerUrl;
4453
+ const settled = await Promise.allSettled(
4454
+ providerConfigs.map(
4455
+ (cfg, i) => this.evaluateParentForProvider(
4456
+ parentTxid,
4457
+ claimedUtxos,
4458
+ cleanVaultAddress,
4459
+ minConfirmations,
4460
+ readers[i],
4461
+ cfg
4462
+ )
4463
+ )
4464
+ );
4465
+ const failures = settled.map((r, i) => ({ r, name: providerName(i) })).filter(
4466
+ (x) => x.r.status === "rejected"
4467
+ ).map(
4468
+ ({ r, name }) => `${name} \u2192 ${r.reason?.message ?? String(r.reason)}`
4469
+ );
4470
+ if (failures.length > 0) {
4471
+ throw new Error(
4472
+ `Self-change classification failed for tx ${parentTxid} on ${failures.length}/${providerConfigs.length} provider(s): ${failures.join("; ")}`
4473
+ );
4474
+ }
4475
+ const evaluations = settled.map(
4476
+ (r) => r.value
4477
+ );
4478
+ const votes = evaluations.map((e) => e.selfSpend);
4479
+ const disagreements = votes.map((vote, i) => ({ vote, name: providerName(i) })).filter((x) => x.vote !== votes[0]).map(
4480
+ (x) => `${providerName(0)}=${votes[0]} vs ${x.name}=${x.vote}`
4481
+ );
4482
+ if (disagreements.length > 0) {
4483
+ throw new Error(
4484
+ `Self-change consensus mismatch for tx ${parentTxid}: ${disagreements.join("; ")}. A provider may be compromised or out of sync; refusing to sign.`
4485
+ );
4486
+ }
4487
+ if (!votes[0]) {
4488
+ return false;
4489
+ }
4490
+ const outputMismatches = evaluations.map((e, i) => e.outputMismatch ? `${providerName(i)} \u2192 ${e.outputMismatch}` : null).filter((m) => m !== null);
4491
+ if (outputMismatches.length > 0) {
4492
+ throw new Error(
4493
+ `Self-change output claim mismatch for tx ${parentTxid}: ${outputMismatches.join("; ")}. The reported UTXO set contradicts the parent transaction; refusing to sign.`
4494
+ );
4495
+ }
4496
+ return true;
4497
+ }
4498
+ /**
4499
+ * Single provider's evaluation of a parent tx: the self-spend proof plus —
4500
+ * when the proof holds — corroboration of the claimed outputs against this
4501
+ * provider's view of the parent's vout set. The output check is skipped for
4502
+ * non-self-spend parents (their UTXOs are excluded regardless, and a
4503
+ * third-party deposit's values are not ours to police).
4504
+ */
4505
+ async evaluateParentForProvider(parentTxid, claimedUtxos, cleanVaultAddress, minConfirmations, reader, providerConfig) {
4506
+ const parentTx = await this.fetchParentTransaction(
4507
+ parentTxid,
4508
+ providerConfig
4509
+ );
4510
+ if (!parentTx || parentTx.inputs.length === 0) {
4511
+ return { selfSpend: false, outputMismatch: null };
4512
+ }
4513
+ const selfSpend = await this.verifyInputsAreSelfSpend(
4514
+ parentTx.inputs,
4515
+ cleanVaultAddress,
4516
+ minConfirmations,
4517
+ reader
4518
+ );
4519
+ const outputMismatch = selfSpend ? findOutputClaimMismatch(claimedUtxos, parentTx.outputs, cleanVaultAddress) : null;
4520
+ return { selfSpend, outputMismatch };
4521
+ }
4522
+ /**
4523
+ * Verify that a transaction's inputs spend exclusively vault-owned,
4524
+ * confirmed outputs, using a single provider's view of the chain.
4525
+ *
4526
+ * @param inputs - Parsed inputs of the parent transaction
4527
+ * @param cleanVaultAddress - Vault address with network prefix stripped
4528
+ * @param minConfirmations - Required depth for every funding transaction
4529
+ * @param reader - Data provider for funding-tx lookups (same provider the inputs came from)
4530
+ * @returns True only when every input is a confirmed, vault-owned outpoint
4531
+ */
4532
+ async verifyInputsAreSelfSpend(inputs, cleanVaultAddress, minConfirmations, reader) {
4533
+ const fundingTxids = [...new Set(inputs.map((input) => input.txid))];
4534
+ const outputsByFundingTx = /* @__PURE__ */ new Map();
4535
+ const needsOutputLookup = fundingTxids.filter(
4536
+ (fundingTxid) => inputs.some(
4537
+ (input) => input.txid === fundingTxid && input.prevoutAddress === void 0
4538
+ )
4539
+ );
4540
+ await Promise.all(
4541
+ needsOutputLookup.map(async (fundingTxid) => {
4542
+ outputsByFundingTx.set(
4543
+ fundingTxid,
4544
+ await reader.getTransactionOutputs(fundingTxid)
4545
+ );
4546
+ })
4547
+ );
4548
+ for (const input of inputs) {
4549
+ let ownerAddress = input.prevoutAddress;
4550
+ if (ownerAddress === void 0) {
4551
+ const outputs = outputsByFundingTx.get(input.txid);
4552
+ ownerAddress = outputs?.find(
4553
+ (output) => output.index === input.vout
4554
+ )?.scriptpubkey_address;
4555
+ }
4556
+ if (ownerAddress !== cleanVaultAddress) {
4557
+ return false;
4558
+ }
4559
+ }
4560
+ const fundingStatuses = await Promise.all(
4561
+ fundingTxids.map((fundingTxid) => reader.getTransaction(fundingTxid))
4562
+ );
4563
+ for (const status of fundingStatuses) {
4564
+ if (!status || status.confirmations < minConfirmations) {
4565
+ return false;
4566
+ }
4567
+ }
4568
+ return true;
4569
+ }
4570
+ /**
4571
+ * Fetch a parent transaction's inputs (vins) and outputs (vouts) via Esplora.
4572
+ *
4573
+ * A module-private fetch (not a `BitcoinDataProvider` method) so editing this
4574
+ * consensus logic does not rebundle the shared provider module's other
4575
+ * importers — see this file's header for the ripple-set constraint.
4576
+ *
4577
+ * @param txid - Transaction ID
4578
+ * @param providerConfig - Esplora endpoint to query (one of the configured
4579
+ * consensus providers)
4580
+ * @returns Parsed inputs + outputs, or null ONLY when the tx is provably not
4581
+ * a self-spend candidate (coinbase-funded). Provider inability —
4582
+ * 404, malformed vin rows, missing vin array — THROWS (review
4583
+ * 2026-07-15 P1): the parent of a UTXO the primary lists must
4584
+ * exist, so "can't fetch it" is a provider failure, and treating it
4585
+ * as a false self-change vote would silently deflate collateral
4586
+ * (wrongful-liquidation direction) if every provider failed the
4587
+ * same way.
4588
+ */
4589
+ async fetchParentTransaction(txid, providerConfig) {
4590
+ if (!providerConfig.providerUrl) {
4591
+ return null;
4592
+ }
4593
+ const timeout = providerConfig.timeout || 15e3;
4594
+ const controller = new AbortController();
4595
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
4596
+ try {
4597
+ const response = await fetch(`${providerConfig.providerUrl}/tx/${txid}`, {
4598
+ signal: controller.signal,
4599
+ headers: await authHeaders(providerConfig.auth, timeout)
4600
+ });
4601
+ if (!response.ok) {
4602
+ if (response.status === 404) {
4603
+ throw new Error(
4604
+ `parent tx ${txid} not found on this provider \u2014 cannot verify the self-change proof`
4605
+ );
4606
+ }
4607
+ throw new Error(
4608
+ `Bitcoin provider error: ${response.status} ${response.statusText}`
4609
+ );
4610
+ }
4611
+ const data = await response.json();
4612
+ if (!data.vin || !Array.isArray(data.vin) || data.vin.length === 0) {
4613
+ throw new Error(
4614
+ `provider returned tx ${txid} without a vin array \u2014 malformed response`
4615
+ );
4616
+ }
4617
+ const inputs = [];
4618
+ for (const vin of data.vin) {
4619
+ if (vin.is_coinbase) {
4620
+ return null;
4621
+ }
4622
+ if (vin.txid === void 0 || vin.vout === void 0) {
4623
+ throw new Error(
4624
+ `provider returned tx ${txid} with a malformed vin row (missing txid/vout)`
4625
+ );
4626
+ }
4627
+ inputs.push({
4628
+ txid: vin.txid,
4629
+ vout: vin.vout,
4630
+ prevoutAddress: vin.prevout?.scriptpubkey_address
4631
+ });
4632
+ }
4633
+ const outputs = Array.isArray(data.vout) ? data.vout.map((vout, index) => ({
4634
+ index,
4635
+ scriptpubkey_address: vout?.scriptpubkey_address,
4636
+ value: typeof vout?.value === "number" ? BigInt(vout.value) : void 0
4637
+ })) : [];
4638
+ return { inputs, outputs };
4639
+ } catch (error) {
4640
+ if (isAbortError(error)) {
4641
+ throw new Error(`Request timeout after ${timeout}ms`);
4642
+ }
4643
+ throw error;
4644
+ } finally {
4645
+ clearTimeout(timeoutId);
4646
+ }
4647
+ }
4648
+ };
4053
4649
  var VaultBalanceModule = class {
4054
4650
  constructor(config) {
4651
+ if (!Number.isInteger(config.minConfirmations) || config.minConfirmations < 1) {
4652
+ throw new Error(
4653
+ `VaultBalanceModule: minConfirmations must be a positive integer (got ${config.minConfirmations}). Pass the policy-derived balance-confirmation threshold \u2014 there is no default.`
4654
+ );
4655
+ }
4055
4656
  this.config = config;
4056
4657
  this.bitcoinProvider = config.bitcoinProvider;
4057
4658
  }
4058
4659
  /**
4059
- * Calculate balance (fast path - no UTXO validation)
4660
+ * Calculate balance (arithmetic only - no UTXO validation)
4060
4661
  *
4061
- * Fast balance-only calculation that skips UTXO validation.
4062
- * Uses simple arithmetic: Total Balance - Authorized Balance = Available Balance
4662
+ * Balance-only calculation: Total Balance - Authorized Balance = Available.
4063
4663
  *
4064
- * Use this when you only need the balance amount and don't need:
4065
- * - Individual UTXO details
4066
- * - Validation that authorized UTXOs were actually spent
4664
+ * ⚠️ SECURITY (remediation P1/#5): this result skips UTXO validation, the
4665
+ * confirmed-consensus gate, and self-change proofing. It must NEVER feed a
4666
+ * signed BalanceConfirmation or any authorization decision. No production
4667
+ * action calls it (the `skipUTXOValidation` fast path that used to route
4668
+ * here was deleted); it exists for read-only diagnostics and tests. Adding
4669
+ * a production caller requires Master sign-off.
4067
4670
  *
4068
4671
  * @param positionId - The position ID to check
4069
4672
  * @param vaultAddress - Bitcoin address of the vault
@@ -4110,17 +4713,18 @@ ${auth.clientId}`;
4110
4713
  * 1. Authorized transaction not found on network (not broadcasted)
4111
4714
  * 2. Authorized transaction has insufficient confirmations (network updating)
4112
4715
  *
4716
+ * NOTE: the `skipUTXOValidation` fast path was removed (remediation P1/#5).
4717
+ * Every trusted-balance read runs full UTXO validation + consensus; a
4718
+ * balance that skipped validation must never feed a signed
4719
+ * BalanceConfirmation.
4720
+ *
4113
4721
  * @param positionId - The position ID to check
4114
4722
  * @param vaultAddress - Bitcoin address of the vault
4115
- * @param skipUTXOValidation - If true, use fast balance-only path (skips UTXO validation)
4116
4723
  * @returns Complete breakdown of vault balance
4117
4724
  * @throws Error if authorized spends are in invalid state
4118
4725
  */
4119
- async calculateTrustedBalance(positionId, vaultAddress, skipUTXOValidation) {
4120
- if (skipUTXOValidation) {
4121
- return await this.calculateBalance(positionId, vaultAddress);
4122
- }
4123
- const minConfirmations = this.config.minConfirmations || 6;
4726
+ async calculateTrustedBalance(positionId, vaultAddress) {
4727
+ const minConfirmations = this.config.minConfirmations;
4124
4728
  const utxoSet = await this.bitcoinProvider.getUTXOSet(
4125
4729
  vaultAddress,
4126
4730
  minConfirmations
@@ -4140,8 +4744,25 @@ ${auth.clientId}`;
4140
4744
  (sum, spend) => sum + spend.satoshis,
4141
4745
  0n
4142
4746
  );
4747
+ const consensus = new VaultBalanceConsensus(
4748
+ this.bitcoinProvider,
4749
+ this.config.bitcoinProviderConfigs,
4750
+ // Absent ⇒ 1: dev/test constructions without a policy keep the
4751
+ // single-provider baseline; validators pass policy.minConsensusProviders.
4752
+ this.config.minConsensusProviders ?? 1
4753
+ );
4754
+ const confirmedConsensusKeys = await consensus.confirmedUtxoConsensus(
4755
+ utxoSet.utxos,
4756
+ vaultAddress,
4757
+ minConfirmations
4758
+ );
4759
+ const selfChangeKeys = await consensus.classifySelfChange(
4760
+ utxoSet.utxos,
4761
+ vaultAddress,
4762
+ minConfirmations
4763
+ );
4143
4764
  const availableUTXOs = utxoSet.utxos.filter(
4144
- (utxo) => !this.isUTXOAuthorized(utxo, authorizedSpends)
4765
+ (utxo) => (confirmedConsensusKeys.has(`${utxo.txid}:${utxo.vout}`) || selfChangeKeys.has(`${utxo.txid}:${utxo.vout}`)) && !this.isUTXOAuthorized(utxo, authorizedSpends)
4145
4766
  );
4146
4767
  const availableBalance = availableUTXOs.reduce(
4147
4768
  (sum, utxo) => sum + utxo.satoshis,
@@ -4373,14 +4994,84 @@ ${auth.clientId}`;
4373
4994
  function createVaultBalanceModule(config) {
4374
4995
  return new VaultBalanceModule(config);
4375
4996
  }
4997
+ var POLICY_BY_CHAIN = {
4998
+ [
4999
+ "hardhat"
5000
+ /* HARDHAT */
5001
+ ]: {
5002
+ chain: "hardhat",
5003
+ chainId: CHAIN_TO_CHAIN_ID[
5004
+ "hardhat"
5005
+ /* HARDHAT */
5006
+ ],
5007
+ bitcoinNetwork: CHAIN_TO_BITCOIN_NETWORK[
5008
+ "hardhat"
5009
+ /* HARDHAT */
5010
+ ],
5011
+ allowArbitraryBitcoinProvider: true,
5012
+ allowArbitraryEvmRpc: true,
5013
+ allowDevSinglePriceProvider: true,
5014
+ minBitcoinBalanceConfirmations: 1,
5015
+ minBitcoinSpendConfirmations: 1,
5016
+ minConsensusProviders: 1
5017
+ },
5018
+ [
5019
+ "sepolia"
5020
+ /* SEPOLIA */
5021
+ ]: {
5022
+ chain: "sepolia",
5023
+ chainId: CHAIN_TO_CHAIN_ID[
5024
+ "sepolia"
5025
+ /* SEPOLIA */
5026
+ ],
5027
+ bitcoinNetwork: CHAIN_TO_BITCOIN_NETWORK[
5028
+ "sepolia"
5029
+ /* SEPOLIA */
5030
+ ],
5031
+ allowArbitraryBitcoinProvider: false,
5032
+ allowArbitraryEvmRpc: false,
5033
+ allowDevSinglePriceProvider: false,
5034
+ minBitcoinBalanceConfirmations: 1,
5035
+ minBitcoinSpendConfirmations: 1,
5036
+ minConsensusProviders: 1
5037
+ },
5038
+ [
5039
+ "ethereum"
5040
+ /* ETHEREUM */
5041
+ ]: {
5042
+ chain: "ethereum",
5043
+ chainId: CHAIN_TO_CHAIN_ID[
5044
+ "ethereum"
5045
+ /* ETHEREUM */
5046
+ ],
5047
+ bitcoinNetwork: CHAIN_TO_BITCOIN_NETWORK[
5048
+ "ethereum"
5049
+ /* ETHEREUM */
5050
+ ],
5051
+ allowArbitraryBitcoinProvider: false,
5052
+ allowArbitraryEvmRpc: false,
5053
+ allowDevSinglePriceProvider: false,
5054
+ minBitcoinBalanceConfirmations: 6,
5055
+ minBitcoinSpendConfirmations: 6,
5056
+ minConsensusProviders: 2
5057
+ }
5058
+ };
5059
+ function getChainPolicy(chain) {
5060
+ const validated = validateChain(chain);
5061
+ const policy = POLICY_BY_CHAIN[validated];
5062
+ if (!policy) {
5063
+ throw new Error(`No chain policy registered for "${chain}"`);
5064
+ }
5065
+ return policy;
5066
+ }
4376
5067
  async function executeVaultSnapshot(params) {
4377
5068
  global.ethers = {
4378
- ...import_ethers15.ethers,
5069
+ ...import_ethers16.ethers,
4379
5070
  providers: {
4380
- StaticJsonRpcProvider: import_ethers15.ethers.JsonRpcProvider
5071
+ StaticJsonRpcProvider: import_ethers16.ethers.JsonRpcProvider
4381
5072
  },
4382
- Contract: import_ethers15.ethers.Contract,
4383
- utils: import_ethers15.ethers
5073
+ Contract: import_ethers16.ethers.Contract,
5074
+ utils: import_ethers16.ethers
4384
5075
  // v6 moved utils to top level
4385
5076
  };
4386
5077
  global.Lit = {
@@ -4417,7 +5108,7 @@ ${auth.clientId}`;
4417
5108
  // Default to mainnet if not provided
4418
5109
  rpcUrl: params.rpcUrl,
4419
5110
  bitcoinProvider,
4420
- minConfirmations: params.minConfirmations || 6
5111
+ minConfirmations: params.minConfirmations ?? getChainPolicy(params.chain).minBitcoinBalanceConfirmations
4421
5112
  });
4422
5113
  const vaultSnapshot = createVaultSnapshotModule({
4423
5114
  contractAddress: params.contractAddress,
@@ -4651,7 +5342,8 @@ function getSepoliaConfig() {
4651
5342
  contractVersionRegistry: SEPOLIA_CONTRACTS.ContractVersionRegistry || "",
4652
5343
  feeRecipientRegistry: SEPOLIA_CONTRACTS.FeeRecipientRegistry || "",
4653
5344
  positionDelegateRegistry: SEPOLIA_CONTRACTS.PositionDelegateRegistry || "",
4654
- bitcoinWithdrawalAddressRegistry: SEPOLIA_CONTRACTS.BitcoinWithdrawalAddressRegistry || ""
5345
+ bitcoinWithdrawalAddressRegistry: SEPOLIA_CONTRACTS.BitcoinWithdrawalAddressRegistry || "",
5346
+ carryAgentRegistry: SEPOLIA_CONTRACTS.CarryAgentRegistry || ""
4655
5347
  },
4656
5348
  subgraphs: {
4657
5349
  // Lazy getter for the same module-init snapshot reason as serviceEndpoint.
@@ -4691,7 +5383,8 @@ function getMainnetConfig() {
4691
5383
  contractVersionRegistry: MAINNET_CONTRACTS.ContractVersionRegistry || "",
4692
5384
  feeRecipientRegistry: MAINNET_CONTRACTS.FeeRecipientRegistry || "",
4693
5385
  positionDelegateRegistry: MAINNET_CONTRACTS.PositionDelegateRegistry || "",
4694
- bitcoinWithdrawalAddressRegistry: MAINNET_CONTRACTS.BitcoinWithdrawalAddressRegistry || ""
5386
+ bitcoinWithdrawalAddressRegistry: MAINNET_CONTRACTS.BitcoinWithdrawalAddressRegistry || "",
5387
+ carryAgentRegistry: MAINNET_CONTRACTS.CarryAgentRegistry || ""
4695
5388
  },
4696
5389
  subgraphs: {
4697
5390
  diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
@@ -4913,7 +5606,7 @@ __export(src_exports, {
4913
5606
  module.exports = __toCommonJS(src_exports);
4914
5607
 
4915
5608
  // src/modules/diamond-hands-sdk.ts
4916
- var import_ethers13 = require("ethers");
5609
+ var import_ethers14 = require("ethers");
4917
5610
 
4918
5611
  // src/types/result.ts
4919
5612
  function success(value) {
@@ -5297,22 +5990,12 @@ var SDKError = class _SDKError extends Error {
5297
5990
 
5298
5991
  // src/utils/chunks/eip1559-broadcast.utils.ts
5299
5992
  var import_ethers = require("ethers");
5300
- var MINT_UCD_GAS_CEILING = BigInt(2e6);
5993
+ var MINT_UCD_GAS_CEILING = BigInt(13e5);
5301
5994
  var WITHDRAW_BTC_GAS_CEILING = BigInt(3e6);
5302
5995
  var MAKE_PAYMENT_GAS_CEILING = BigInt(1e6);
5303
5996
  var EXTEND_POSITION_GAS_CEILING = BigInt(1e6);
5304
5997
  var LIQUIDATION_COMMIT_GAS_CEILING = BigInt(1e6);
5305
5998
  var LIQUIDATION_REVEAL_GAS_CEILING = BigInt(2e6);
5306
- var MINT_UCD_ESTIMATE_MARGIN_BPS = 2500;
5307
- async function estimateContractCallGasWithMargin(provider, from, to, data, marginBps, ceiling) {
5308
- try {
5309
- const estimated = await provider.estimateGas({ from, to, data });
5310
- const withMargin = estimated * BigInt(1e4 + marginBps) / 10000n;
5311
- return withMargin > ceiling ? ceiling : withMargin;
5312
- } catch {
5313
- return null;
5314
- }
5315
- }
5316
5999
  async function resolveEip1559FeeFields(provider) {
5317
6000
  const feeData = await provider.getFeeData();
5318
6001
  let maxFeePerGas = feeData.maxFeePerGas ?? void 0;
@@ -5820,21 +6503,117 @@ async function buildSignedLoginPayload(signer, chainId, audience) {
5820
6503
  return { chainId, message, signature };
5821
6504
  }
5822
6505
 
6506
+ // src/utils/server-session-store.ts
6507
+ var REAUTH_WINDOW_SECONDS = 24 * 60 * 60;
6508
+ var SAFETY_MARGIN_SECONDS = 5 * 60;
6509
+ var STORE_PREFIX = "dh-server-session:";
6510
+ function sessionStoreKey(address, chainId, endpoint) {
6511
+ return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
6512
+ }
6513
+ function isPayloadShape(value) {
6514
+ return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
6515
+ }
6516
+ function isPersistedShape(value) {
6517
+ return !!value && typeof value === "object" && value.version === 1 && typeof value.address === "string" && typeof value.chainId === "number" && typeof value.endpoint === "string" && isPayloadShape(value.payload) && (value.token === void 0 || typeof value.token === "string") && (value.tokenExpiresAt === void 0 || typeof value.tokenExpiresAt === "number");
6518
+ }
6519
+ var LocalStorageSessionStore = class {
6520
+ load(key) {
6521
+ let raw;
6522
+ try {
6523
+ raw = window.localStorage.getItem(key);
6524
+ } catch {
6525
+ return null;
6526
+ }
6527
+ if (!raw)
6528
+ return null;
6529
+ let parsed;
6530
+ try {
6531
+ parsed = JSON.parse(raw);
6532
+ } catch {
6533
+ this.clear(key);
6534
+ return null;
6535
+ }
6536
+ if (!isPersistedShape(parsed)) {
6537
+ this.clear(key);
6538
+ return null;
6539
+ }
6540
+ return parsed;
6541
+ }
6542
+ save(key, session) {
6543
+ try {
6544
+ window.localStorage.setItem(key, JSON.stringify(session));
6545
+ } catch {
6546
+ }
6547
+ }
6548
+ clear(key) {
6549
+ try {
6550
+ window.localStorage.removeItem(key);
6551
+ } catch {
6552
+ }
6553
+ }
6554
+ };
6555
+ var MemorySessionStore = class {
6556
+ entries = /* @__PURE__ */ new Map();
6557
+ load(key) {
6558
+ return this.entries.get(key) ?? null;
6559
+ }
6560
+ save(key, session) {
6561
+ this.entries.set(key, session);
6562
+ }
6563
+ clear(key) {
6564
+ this.entries.delete(key);
6565
+ }
6566
+ };
6567
+ function localStorageUsable() {
6568
+ try {
6569
+ if (typeof window === "undefined" || !window.localStorage)
6570
+ return false;
6571
+ const probe = "__dh_server_session_probe__";
6572
+ window.localStorage.setItem(probe, "1");
6573
+ window.localStorage.removeItem(probe);
6574
+ return true;
6575
+ } catch {
6576
+ return false;
6577
+ }
6578
+ }
6579
+ function createDefaultSessionStore() {
6580
+ return localStorageUsable() ? new LocalStorageSessionStore() : new MemorySessionStore();
6581
+ }
6582
+
5823
6583
  // src/utils/server-session.ts
5824
6584
  var REFRESH_LEEWAY_SECONDS = 30;
6585
+ var ServerLoginError = class extends Error {
6586
+ constructor(message, status) {
6587
+ super(message);
6588
+ this.status = status;
6589
+ this.name = "ServerLoginError";
6590
+ }
6591
+ };
6592
+ function isLoginRejection(error) {
6593
+ return error instanceof ServerLoginError && error.status >= 400 && error.status < 500 && error.status !== 429;
6594
+ }
5825
6595
  var ServerSession = class {
5826
6596
  signer;
5827
6597
  serviceEndpoint;
5828
6598
  chainId;
6599
+ store;
6600
+ onSignaturePrompt;
5829
6601
  now;
5830
6602
  fetchImpl;
5831
6603
  cached = null;
5832
6604
  inFlight = null;
6605
+ /**
6606
+ * Store key for the current signer, memoized on first use so the sync
6607
+ * `clear()` can drop the persisted token without an async address lookup.
6608
+ */
6609
+ lastStoreKey = null;
5833
6610
  constructor(opts) {
5834
6611
  assertSafeServiceEndpoint(opts.serviceEndpoint);
5835
6612
  this.signer = opts.signer;
5836
6613
  this.serviceEndpoint = opts.serviceEndpoint.replace(/\/+$/, "");
5837
6614
  this.chainId = opts.chainId;
6615
+ this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
6616
+ this.onSignaturePrompt = opts.onSignaturePrompt;
5838
6617
  this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
5839
6618
  this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
5840
6619
  }
@@ -5847,19 +6626,56 @@ var ServerSession = class {
5847
6626
  async getAuthHeader() {
5848
6627
  return { Authorization: `Bearer ${await this.getValidToken()}` };
5849
6628
  }
5850
- /** Drop the cached token (e.g. on 401 from server). Next call re-logs in. */
6629
+ /**
6630
+ * Drop the cached token (e.g. on 401 from server). The persisted envelope is
6631
+ * kept — the next call re-mints silently — but its stored token copy is
6632
+ * dropped too so a dead JWT can't be re-adopted from the store.
6633
+ */
5851
6634
  clear() {
5852
6635
  this.cached = null;
5853
6636
  this.inFlight = null;
6637
+ if (!this.store || !this.lastStoreKey)
6638
+ return;
6639
+ const persisted = this.store.load(this.lastStoreKey);
6640
+ if (persisted?.token) {
6641
+ this.store.save(this.lastStoreKey, {
6642
+ ...persisted,
6643
+ token: void 0,
6644
+ tokenExpiresAt: void 0
6645
+ });
6646
+ }
5854
6647
  }
5855
6648
  /**
5856
- * Revoke the current session JWT on lit-ops-server (`POST /api/auth/logout`).
5857
- * Clears the local cache regardless of server response so the client stops
5858
- * presenting the token.
6649
+ * Drop the session AND the persisted re-mint envelope (wallet disconnect /
6650
+ * explicit sign-out paths — anything that must remove the 24h credential
6651
+ * from storage).
6652
+ */
6653
+ async clearPersisted() {
6654
+ this.cached = null;
6655
+ this.inFlight = null;
6656
+ if (!this.store)
6657
+ return;
6658
+ if (this.lastStoreKey) {
6659
+ this.store.clear(this.lastStoreKey);
6660
+ return;
6661
+ }
6662
+ try {
6663
+ const address = (await this.signer.getAddress()).toLowerCase();
6664
+ this.store.clear(
6665
+ sessionStoreKey(address, this.chainId, this.serviceEndpoint)
6666
+ );
6667
+ } catch {
6668
+ }
6669
+ }
6670
+ /**
6671
+ * Revoke the current session JWT on lit-ops-server (`POST /api/auth/logout`)
6672
+ * — which also tombstones the envelope's re-mint window server-side — and
6673
+ * remove the persisted credential locally. Clears the local cache regardless
6674
+ * of server response so the client stops presenting the token.
5859
6675
  */
5860
6676
  async logout() {
5861
6677
  const token = this.cached?.token ?? await this.getValidToken().catch(() => null);
5862
- this.clear();
6678
+ await this.clearPersisted();
5863
6679
  if (!token)
5864
6680
  return;
5865
6681
  const res = await this.fetchImpl(`${this.serviceEndpoint}/api/auth/logout`, {
@@ -5882,41 +6698,116 @@ var ServerSession = class {
5882
6698
  return this.inFlight;
5883
6699
  this.inFlight = (async () => {
5884
6700
  try {
6701
+ const persisted = await this.loadPersisted();
6702
+ if (persisted) {
6703
+ if (persisted.token && typeof persisted.tokenExpiresAt === "number" && persisted.tokenExpiresAt - REFRESH_LEEWAY_SECONDS > this.now()) {
6704
+ this.cached = {
6705
+ token: persisted.token,
6706
+ expiresAt: persisted.tokenExpiresAt
6707
+ };
6708
+ return this.cached;
6709
+ }
6710
+ try {
6711
+ const session2 = await this.login(persisted.payload);
6712
+ this.persist(persisted.payload, session2);
6713
+ this.cached = session2;
6714
+ return session2;
6715
+ } catch (error) {
6716
+ if (!isLoginRejection(error))
6717
+ throw error;
6718
+ if (this.store && this.lastStoreKey) {
6719
+ this.store.clear(this.lastStoreKey);
6720
+ }
6721
+ }
6722
+ }
6723
+ this.onSignaturePrompt?.();
5885
6724
  const payload = await buildSignedLoginPayload(
5886
6725
  this.signer,
5887
6726
  this.chainId,
5888
- this.serviceEndpoint
5889
- );
5890
- const res = await this.fetchImpl(
5891
- `${this.serviceEndpoint}/api/auth/login`,
5892
- {
5893
- method: "POST",
5894
- headers: { "Content-Type": "application/json" },
5895
- body: JSON.stringify(payload)
5896
- }
5897
- );
5898
- if (!res.ok) {
5899
- const detail = await res.text().catch(() => "");
5900
- throw new Error(
5901
- `lit-ops-server login failed: HTTP ${res.status} ${detail.slice(0, 200)}`
5902
- );
5903
- }
5904
- const body = await res.json();
5905
- if (!body?.token || !body?.expiresAt) {
5906
- throw new Error("lit-ops-server login returned malformed response");
5907
- }
5908
- this.cached = { token: body.token, expiresAt: body.expiresAt };
5909
- return this.cached;
6727
+ this.serviceEndpoint
6728
+ );
6729
+ const session = await this.login(payload);
6730
+ this.persist(payload, session);
6731
+ this.cached = session;
6732
+ return session;
5910
6733
  } finally {
5911
6734
  this.inFlight = null;
5912
6735
  }
5913
6736
  })();
5914
6737
  return this.inFlight;
5915
6738
  }
6739
+ async login(payload) {
6740
+ const res = await this.fetchImpl(`${this.serviceEndpoint}/api/auth/login`, {
6741
+ method: "POST",
6742
+ headers: { "Content-Type": "application/json" },
6743
+ body: JSON.stringify(payload)
6744
+ });
6745
+ if (!res.ok) {
6746
+ const detail = await res.text().catch(() => "");
6747
+ throw new ServerLoginError(
6748
+ `lit-ops-server login failed: HTTP ${res.status} ${detail.slice(0, 200)}`,
6749
+ res.status
6750
+ );
6751
+ }
6752
+ const body = await res.json();
6753
+ if (!body?.token || !body?.expiresAt) {
6754
+ throw new Error("lit-ops-server login returned malformed response");
6755
+ }
6756
+ return { token: body.token, expiresAt: body.expiresAt };
6757
+ }
6758
+ /**
6759
+ * Load the persisted session for the current signer, validating that it
6760
+ * belongs to this address/chain/endpoint and that the envelope is still
6761
+ * inside the re-auth window (with a safety margin). Invalid or expired
6762
+ * entries are evicted.
6763
+ */
6764
+ async loadPersisted() {
6765
+ if (!this.store)
6766
+ return null;
6767
+ try {
6768
+ const address = (await this.signer.getAddress()).toLowerCase();
6769
+ const key = sessionStoreKey(address, this.chainId, this.serviceEndpoint);
6770
+ this.lastStoreKey = key;
6771
+ const persisted = this.store.load(key);
6772
+ if (!persisted)
6773
+ return null;
6774
+ const withinWindow = this.now() < persisted.payload.message.issuedAt + REAUTH_WINDOW_SECONDS - SAFETY_MARGIN_SECONDS;
6775
+ if (persisted.address !== address || persisted.chainId !== this.chainId || persisted.endpoint !== this.serviceEndpoint || persisted.payload.message.address.toLowerCase() !== address || !withinWindow) {
6776
+ this.store.clear(key);
6777
+ return null;
6778
+ }
6779
+ return persisted;
6780
+ } catch {
6781
+ return null;
6782
+ }
6783
+ }
6784
+ /** Best-effort save of the envelope + live JWT for silent recovery. */
6785
+ persist(payload, session) {
6786
+ if (!this.store)
6787
+ return;
6788
+ const address = payload.message.address.toLowerCase();
6789
+ try {
6790
+ const key = sessionStoreKey(address, this.chainId, this.serviceEndpoint);
6791
+ this.lastStoreKey = key;
6792
+ this.store.save(key, {
6793
+ version: 1,
6794
+ address,
6795
+ chainId: this.chainId,
6796
+ endpoint: this.serviceEndpoint,
6797
+ payload,
6798
+ token: session.token,
6799
+ tokenExpiresAt: session.expiresAt
6800
+ });
6801
+ } catch {
6802
+ }
6803
+ }
5916
6804
  };
5917
6805
 
5918
6806
  // src/utils/quantum-revert.utils.ts
6807
+ var import_ethers5 = require("ethers");
5919
6808
  var DEAD_ZONE_VIOLATION_SELECTOR = "0xbe4b82c1";
6809
+ var ERROR_STRING_SELECTOR = "0x08c379a0";
6810
+ var PANIC_SELECTOR = "0x4e487b71";
5920
6811
  var QUANTUM_REVERT_NAMES = {
5921
6812
  "0xbe4b82c1": "DeadZoneViolation()",
5922
6813
  "0x137f3b70": "InDeadZone()",
@@ -5940,14 +6831,128 @@ function decodeQuantumRevert(e) {
5940
6831
  e?.error?.data?.data ?? e?.error?.data ?? e?.error?.error?.data;
5941
6832
  const data = typeof raw === "string" && raw.startsWith("0x") ? raw : null;
5942
6833
  const selector = data && data.length >= 10 ? data.slice(0, 10) : null;
5943
- const name = selector ? QUANTUM_REVERT_NAMES[selector] ?? null : null;
6834
+ let name = selector ? QUANTUM_REVERT_NAMES[selector] ?? null : null;
6835
+ if (!name && selector && data) {
6836
+ try {
6837
+ if (selector === ERROR_STRING_SELECTOR) {
6838
+ const [message] = import_ethers5.AbiCoder.defaultAbiCoder().decode(
6839
+ ["string"],
6840
+ "0x" + data.slice(10)
6841
+ );
6842
+ name = `Error("${message}")`;
6843
+ } else if (selector === PANIC_SELECTOR) {
6844
+ const [code] = import_ethers5.AbiCoder.defaultAbiCoder().decode(
6845
+ ["uint256"],
6846
+ "0x" + data.slice(10)
6847
+ );
6848
+ name = `Panic(0x${code.toString(16)})`;
6849
+ }
6850
+ } catch {
6851
+ }
6852
+ }
5944
6853
  return { data, selector, name };
5945
6854
  }
5946
6855
 
6856
+ // src/utils/quantum-submission.utils.ts
6857
+ var MAX_DEADZONE_RESIMULATIONS = 3;
6858
+ var QuantumRevertError = class extends Error {
6859
+ constructor(errorName, selector, data, cause) {
6860
+ super(
6861
+ `Pre-send simulation reverted: ${errorName ?? selector ?? "unknown"}`
6862
+ );
6863
+ this.errorName = errorName;
6864
+ this.selector = selector;
6865
+ this.data = data;
6866
+ this.cause = cause;
6867
+ this.name = "QuantumRevertError";
6868
+ }
6869
+ };
6870
+ async function assertSafeQuantumSubmission(opts) {
6871
+ const { provider, to, from, data, quantumTimestamp } = opts;
6872
+ const maxResimulations = opts.maxResimulations ?? MAX_DEADZONE_RESIMULATIONS;
6873
+ const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
6874
+ const onDebug = opts.onDebug ?? (() => {
6875
+ });
6876
+ const gateOpts = {
6877
+ now,
6878
+ ...opts.sleep ? { sleep: opts.sleep } : {}
6879
+ };
6880
+ await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6881
+ for (let sim = 1; ; sim++) {
6882
+ try {
6883
+ await provider.call({ to, from, data });
6884
+ break;
6885
+ } catch (simError) {
6886
+ const decoded = decodeQuantumRevert(simError);
6887
+ if (decoded.selector === DEAD_ZONE_VIOLATION_SELECTOR && sim <= maxResimulations) {
6888
+ const sigQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
6889
+ const realCurrentQuantum = Math.floor(now() / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
6890
+ if (sigQuantum === realCurrentQuantum) {
6891
+ onDebug(
6892
+ "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast"
6893
+ );
6894
+ break;
6895
+ }
6896
+ const gate = await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6897
+ if (gate.waited) {
6898
+ onDebug(
6899
+ `\u{1F501} Re-simulating after crossing the quantum boundary (attempt ${sim}/${maxResimulations})`
6900
+ );
6901
+ continue;
6902
+ }
6903
+ }
6904
+ if (!decoded.data) {
6905
+ const label = simError instanceof Error ? simError.message : String(simError);
6906
+ onDebug(
6907
+ `\u26A0\uFE0F Pre-send simulation failed without revert data (${label}) \u2014 proceeding to broadcast`
6908
+ );
6909
+ break;
6910
+ }
6911
+ throw new QuantumRevertError(
6912
+ decoded.name,
6913
+ decoded.selector,
6914
+ decoded.data,
6915
+ simError
6916
+ );
6917
+ }
6918
+ }
6919
+ await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6920
+ }
6921
+
5947
6922
  // src/utils/btc-withdrawal-message.ts
5948
- var import_ethers5 = require("ethers");
6923
+ var import_ethers6 = require("ethers");
5949
6924
  var QUANTUM_SECONDS = 60;
5950
6925
  var QUANTUM_SAFE_THRESHOLD = 40;
6926
+ function hashBtcInputSet(utxos) {
6927
+ if (!Array.isArray(utxos) || utxos.length === 0) {
6928
+ throw new Error("hashBtcInputSet: at least one input is required");
6929
+ }
6930
+ const normalized = utxos.map((u) => {
6931
+ const txid = String(u.txid).toLowerCase().replace(/^0x/, "");
6932
+ const vout = Number(u.vout);
6933
+ const value = Number(u.value);
6934
+ if (!/^[0-9a-f]{64}$/.test(txid)) {
6935
+ throw new Error(`hashBtcInputSet: invalid txid "${u.txid}"`);
6936
+ }
6937
+ if (!Number.isSafeInteger(vout) || vout < 0) {
6938
+ throw new Error(`hashBtcInputSet: invalid vout ${u.vout} for ${txid}`);
6939
+ }
6940
+ if (!Number.isSafeInteger(value) || value <= 0) {
6941
+ throw new Error(`hashBtcInputSet: invalid value ${u.value} for ${txid}:${vout}`);
6942
+ }
6943
+ return { txid, vout, value };
6944
+ });
6945
+ const seen = /* @__PURE__ */ new Set();
6946
+ for (const u of normalized) {
6947
+ const key = `${u.txid}:${u.vout}`;
6948
+ if (seen.has(key)) {
6949
+ throw new Error(`hashBtcInputSet: duplicate outpoint ${key}`);
6950
+ }
6951
+ seen.add(key);
6952
+ }
6953
+ const canonical = normalized.sort((a, b) => a.txid < b.txid ? -1 : a.txid > b.txid ? 1 : a.vout - b.vout).map((u) => `${u.txid}:${u.vout}:${u.value}`).join("|");
6954
+ return (0, import_ethers6.keccak256)((0, import_ethers6.toUtf8Bytes)(canonical));
6955
+ }
5951
6956
  async function buildBtcExecuteEnvelope(params) {
5952
6957
  const {
5953
6958
  positionId,
@@ -5968,6 +6973,17 @@ async function buildBtcExecuteEnvelope(params) {
5968
6973
  "buildBtcExecuteEnvelope: targetAmount must be a positive number of satoshis"
5969
6974
  );
5970
6975
  }
6976
+ const utxos = params.utxos && params.utxos.length > 0 ? params.utxos : [{ txid, vout, value: satoshis }];
6977
+ const normTxid = (t) => String(t).toLowerCase().replace(/^0x/, "");
6978
+ const authorizedInSet = utxos.some(
6979
+ (u) => normTxid(u.txid) === normTxid(txid) && Number(u.vout) === Number(vout)
6980
+ );
6981
+ if (!authorizedInSet) {
6982
+ throw new Error(
6983
+ `buildBtcExecuteEnvelope: the authorized UTXO ${txid}:${vout} must be part of utxos[]`
6984
+ );
6985
+ }
6986
+ const utxosHash = hashBtcInputSet(utxos);
5971
6987
  const nowSec = Date.now() / 1e3;
5972
6988
  const currentQuantumStart = Math.floor(nowSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
5973
6989
  const secsIntoQuantum = nowSec - currentQuantumStart;
@@ -5980,15 +6996,15 @@ async function buildBtcExecuteEnvelope(params) {
5980
6996
  } else {
5981
6997
  timestamp = currentQuantumStart;
5982
6998
  }
5983
- const actionHash = (0, import_ethers5.keccak256)(
5984
- (0, import_ethers5.toUtf8Bytes)("execute-btc-withdrawal")
6999
+ const actionHash = (0, import_ethers6.keccak256)(
7000
+ (0, import_ethers6.toUtf8Bytes)("execute-btc-withdrawal")
5985
7001
  );
5986
7002
  const utxoIdentifier = `${txid}:${vout}`;
5987
- const positionIdBytes32 = (0, import_ethers5.zeroPadValue)(
7003
+ const positionIdBytes32 = (0, import_ethers6.zeroPadValue)(
5988
7004
  positionId.startsWith("0x") ? positionId : `0x${positionId}`,
5989
7005
  32
5990
7006
  );
5991
- const messageHash = (0, import_ethers5.solidityPackedKeccak256)(
7007
+ const messageHash = (0, import_ethers6.solidityPackedKeccak256)(
5992
7008
  [
5993
7009
  "bytes32",
5994
7010
  // positionId
@@ -6004,6 +7020,8 @@ async function buildBtcExecuteEnvelope(params) {
6004
7020
  // targetAddress (BTC destination)
6005
7021
  "uint256",
6006
7022
  // targetAmount (sats authorized to recipient)
7023
+ "bytes32",
7024
+ // utxosHash (canonical hash of the FULL input set — P6/#8)
6007
7025
  "bytes32"
6008
7026
  // actionHash
6009
7027
  ],
@@ -6015,10 +7033,11 @@ async function buildBtcExecuteEnvelope(params) {
6015
7033
  networkFee,
6016
7034
  targetAddress,
6017
7035
  targetAmount,
7036
+ utxosHash,
6018
7037
  actionHash
6019
7038
  ]
6020
7039
  );
6021
- const userSignature = await signer.signMessage((0, import_ethers5.getBytes)(messageHash));
7040
+ const userSignature = await signer.signMessage((0, import_ethers6.getBytes)(messageHash));
6022
7041
  const borrowerAddress = await signer.getAddress();
6023
7042
  return {
6024
7043
  positionId,
@@ -6031,7 +7050,8 @@ async function buildBtcExecuteEnvelope(params) {
6031
7050
  chainId,
6032
7051
  timestamp,
6033
7052
  userSignature,
6034
- borrowerAddress
7053
+ borrowerAddress,
7054
+ utxos
6035
7055
  };
6036
7056
  }
6037
7057
 
@@ -6081,7 +7101,7 @@ function maybeTemperSignature(signature, shouldTemper, operationName) {
6081
7101
  }
6082
7102
 
6083
7103
  // src/utils/address-conversion.utils.ts
6084
- var import_ethers6 = require("ethers");
7104
+ var import_ethers7 = require("ethers");
6085
7105
 
6086
7106
  // src/utils/chunks/bitcoin-utils.ts
6087
7107
  var import_sha256 = require("@noble/hashes/sha256");
@@ -6365,7 +7385,7 @@ function normalizeBitcoinAddress(address) {
6365
7385
  }
6366
7386
 
6367
7387
  // src/protocol/protocol-pause.ts
6368
- var import_ethers7 = require("ethers");
7388
+ var import_ethers8 = require("ethers");
6369
7389
  var GET_PROTOCOL_PAUSE_STATUS_ABI = [
6370
7390
  "function getProtocolPauseStatus() view returns (tuple(bool positionManagerPaused, bool positionManagerLiquidationsPaused, bool positionManagerCorePaused, bool loanOperationsPaused, bool collateralManagerPaused, bool liquidationManagerPaused, bool circuitBreakerPaused, bool ucdControllerPaused, bool ucdTokenPaused, bool simplePsmPaused))"
6371
7391
  ];
@@ -6387,7 +7407,7 @@ function rowToStatus(row) {
6387
7407
  return row;
6388
7408
  }
6389
7409
  async function fetchProtocolPauseStatus(provider, positionManagerAddress) {
6390
- const iface = new import_ethers7.Interface(GET_PROTOCOL_PAUSE_STATUS_ABI);
7410
+ const iface = new import_ethers8.Interface(GET_PROTOCOL_PAUSE_STATUS_ABI);
6391
7411
  const data = iface.encodeFunctionData("getProtocolPauseStatus", []);
6392
7412
  const raw = await provider.call({ to: positionManagerAddress, data });
6393
7413
  const decoded = iface.decodeFunctionResult("getProtocolPauseStatus", raw);
@@ -6520,7 +7540,7 @@ function extractOptionalLitSignature(raw) {
6520
7540
  }
6521
7541
 
6522
7542
  // src/modules/contract/contract-manager.module.ts
6523
- var import_ethers8 = require("ethers");
7543
+ var import_ethers9 = require("ethers");
6524
7544
  init_deployment_addresses();
6525
7545
  var POSITION_MANAGER_ABI = [
6526
7546
  "function createPosition(bytes32 pkpId, bytes calldata validatorSignature, string mainnetVaultAddress, string regtestVaultAddress, uint256 selectedTermMonths, uint256 validatorVersion, bytes calldata pkpPublicKey) external returns (bytes32 positionId)",
@@ -6597,43 +7617,43 @@ var ContractManager = class {
6597
7617
  }
6598
7618
  const contractRunner = runner;
6599
7619
  const contracts = {
6600
- positionManager: new import_ethers8.Contract(
7620
+ positionManager: new import_ethers9.Contract(
6601
7621
  contractAddresses.positionManager,
6602
7622
  POSITION_MANAGER_ABI,
6603
7623
  contractRunner
6604
7624
  ),
6605
- loanOperationsManager: new import_ethers8.Contract(
6606
- contractAddresses.loanOperationsManager || import_ethers8.ZeroAddress,
7625
+ loanOperationsManager: new import_ethers9.Contract(
7626
+ contractAddresses.loanOperationsManager || import_ethers9.ZeroAddress,
6607
7627
  LOAN_OPS_ABI,
6608
7628
  contractRunner
6609
7629
  ),
6610
- ucdController: new import_ethers8.Contract(
7630
+ ucdController: new import_ethers9.Contract(
6611
7631
  contractAddresses.ucdToken,
6612
7632
  UCD_CONTROLLER_ABI,
6613
7633
  contractRunner
6614
7634
  ),
6615
- priceFeed: new import_ethers8.Contract(
6616
- contractAddresses.priceFeedConsumer || import_ethers8.ZeroAddress,
7635
+ priceFeed: new import_ethers9.Contract(
7636
+ contractAddresses.priceFeedConsumer || import_ethers9.ZeroAddress,
6617
7637
  PRICE_FEED_ABI,
6618
7638
  contractRunner
6619
7639
  ),
6620
- termManager: new import_ethers8.Contract(
6621
- contractAddresses.termManager || import_ethers8.ZeroAddress,
7640
+ termManager: new import_ethers9.Contract(
7641
+ contractAddresses.termManager || import_ethers9.ZeroAddress,
6622
7642
  TERM_MANAGER_ABI,
6623
7643
  contractRunner
6624
7644
  ),
6625
- circuitBreaker: new import_ethers8.Contract(
6626
- contractAddresses.circuitBreaker || import_ethers8.ZeroAddress,
7645
+ circuitBreaker: new import_ethers9.Contract(
7646
+ contractAddresses.circuitBreaker || import_ethers9.ZeroAddress,
6627
7647
  CIRCUIT_BREAKER_ABI,
6628
7648
  contractRunner
6629
7649
  ),
6630
- liquidationManager: new import_ethers8.Contract(
6631
- contractAddresses.liquidationManager || import_ethers8.ZeroAddress,
7650
+ liquidationManager: new import_ethers9.Contract(
7651
+ contractAddresses.liquidationManager || import_ethers9.ZeroAddress,
6632
7652
  LIQUIDATION_MANAGER_ABI,
6633
7653
  contractRunner
6634
7654
  ),
6635
- bitcoinWithdrawalAddressRegistry: new import_ethers8.Contract(
6636
- contractAddresses.bitcoinWithdrawalAddressRegistry || import_ethers8.ZeroAddress,
7655
+ bitcoinWithdrawalAddressRegistry: new import_ethers9.Contract(
7656
+ contractAddresses.bitcoinWithdrawalAddressRegistry || import_ethers9.ZeroAddress,
6637
7657
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_ABI,
6638
7658
  contractRunner
6639
7659
  )
@@ -6683,13 +7703,13 @@ var ContractManager = class {
6683
7703
  const address = addresses[key];
6684
7704
  if (!address) {
6685
7705
  missing.push(String(key));
6686
- } else if (!(0, import_ethers8.isAddress)(address)) {
7706
+ } else if (!(0, import_ethers9.isAddress)(address)) {
6687
7707
  invalid.push(String(key));
6688
7708
  }
6689
7709
  }
6690
7710
  for (const key of optionalAddresses) {
6691
7711
  const address = addresses[key];
6692
- if (address && !(0, import_ethers8.isAddress)(address)) {
7712
+ if (address && !(0, import_ethers9.isAddress)(address)) {
6693
7713
  invalid.push(String(key));
6694
7714
  }
6695
7715
  }
@@ -7665,7 +8685,7 @@ function createPKPManager(config) {
7665
8685
  }
7666
8686
 
7667
8687
  // src/modules/loan/loan-creator.module.ts
7668
- var import_ethers9 = require("ethers");
8688
+ var import_ethers10 = require("ethers");
7669
8689
  var import_dh_lit_actions = __toESM(require_pkg_src());
7670
8690
  var LoanCreator = class {
7671
8691
  config;
@@ -7995,11 +9015,11 @@ var LoanCreator = class {
7995
9015
  try {
7996
9016
  const mainnetVaultAddress = bitcoinAddresses.mainnet;
7997
9017
  const regtestVaultAddress = bitcoinAddresses.regtest;
7998
- const pkpIdBytes32 = (0, import_ethers9.zeroPadValue)(
7999
- (0, import_ethers9.toBeHex)(BigInt(pkpData.tokenId)),
9018
+ const pkpIdBytes32 = (0, import_ethers10.zeroPadValue)(
9019
+ (0, import_ethers10.toBeHex)(BigInt(pkpData.tokenId)),
8000
9020
  32
8001
9021
  );
8002
- const pkpPublicKeyUncompressed = import_ethers9.SigningKey.computePublicKey(
9022
+ const pkpPublicKeyUncompressed = import_ethers10.SigningKey.computePublicKey(
8003
9023
  pkpData.publicKey,
8004
9024
  false
8005
9025
  );
@@ -8012,7 +9032,7 @@ var LoanCreator = class {
8012
9032
  );
8013
9033
  log.debug(`BigInt conversion: ${BigInt(pkpData.tokenId).toString()}`);
8014
9034
  log.debug(
8015
- `Hex from BigInt: ${(0, import_ethers9.toBeHex)(BigInt(pkpData.tokenId))}`
9035
+ `Hex from BigInt: ${(0, import_ethers10.toBeHex)(BigInt(pkpData.tokenId))}`
8016
9036
  );
8017
9037
  log.debug(`Final bytes32 (zero-padded): ${pkpIdBytes32}`);
8018
9038
  log.debug(
@@ -8176,8 +9196,8 @@ var LoanCreator = class {
8176
9196
  }
8177
9197
  }
8178
9198
  if (this.config.debug) {
8179
- const pkpIdBytes32Check = (0, import_ethers9.zeroPadValue)(
8180
- (0, import_ethers9.toBeHex)(BigInt(pkpData.tokenId)),
9199
+ const pkpIdBytes32Check = (0, import_ethers10.zeroPadValue)(
9200
+ (0, import_ethers10.toBeHex)(BigInt(pkpData.tokenId)),
8181
9201
  32
8182
9202
  );
8183
9203
  log.error("\u274C Loan creation transaction failed", {
@@ -8223,8 +9243,8 @@ var LoanCreator = class {
8223
9243
  const newEventAbi = [
8224
9244
  "event PositionCreated(bytes32 indexed positionId, bytes32 indexed pkpId, address indexed borrower, uint256 requestedCollateralRatio, uint256 selectedTerm, uint256 expiryAt)"
8225
9245
  ];
8226
- const oldIface = new import_ethers9.Interface(oldEventAbi);
8227
- const newIface = new import_ethers9.Interface(newEventAbi);
9246
+ const oldIface = new import_ethers10.Interface(oldEventAbi);
9247
+ const newIface = new import_ethers10.Interface(newEventAbi);
8228
9248
  let positionId;
8229
9249
  if (this.config.debug) {
8230
9250
  log.info("\u{1F50D} Parsing transaction receipt logs", {
@@ -8318,7 +9338,7 @@ var LoanStatus = /* @__PURE__ */ ((LoanStatus2) => {
8318
9338
  })(LoanStatus || {});
8319
9339
 
8320
9340
  // src/modules/loan/loan-query.module.ts
8321
- var import_ethers10 = require("ethers");
9341
+ var import_ethers11 = require("ethers");
8322
9342
  var POSITION_CORE_ABI = [
8323
9343
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
8324
9344
  ];
@@ -8616,7 +9636,7 @@ var LoanQuery = class {
8616
9636
  const isChipotlePkpId2 = typeof rawId === "string" && rawId.length === 40 && !rawId.toLowerCase().startsWith("04");
8617
9637
  if (isChipotlePkpId2) {
8618
9638
  if (this.config.positionManagerCoreAddress && this.config.provider) {
8619
- const pmContract = new import_ethers10.Contract(
9639
+ const pmContract = new import_ethers11.Contract(
8620
9640
  this.config.positionManagerCoreAddress,
8621
9641
  POSITION_CORE_ABI,
8622
9642
  this.config.provider
@@ -8847,6 +9867,56 @@ var LoanQuery = class {
8847
9867
  })
8848
9868
  );
8849
9869
  }
9870
+ /**
9871
+ * Get loan-event history for every position owned by a wallet.
9872
+ *
9873
+ * Composes getUserPositions + getLoanEvents (the subgraph has no wallet-scoped
9874
+ * event collection). Each returned LoanEvents carries its own positionId, so
9875
+ * callers can flatten and merge into a single per-wallet timeline.
9876
+ *
9877
+ * Bitcoin deposits never appear in the subgraph — merge those in from an
9878
+ * Esplora source on the consumer side.
9879
+ *
9880
+ * @param userAddress - Loan-owner wallet address.
9881
+ * @param filter - Optional per-position event filter (same semantics as getLoanEvents).
9882
+ * @returns One LoanEvents object per position (empty array if the wallet has none).
9883
+ *
9884
+ * @example
9885
+ * ```typescript
9886
+ * const result = await loanQuery.getWalletLoanEvents('0x830b...');
9887
+ * if (result.success) {
9888
+ * const allEvents = result.value; // LoanEvents[] — one per position
9889
+ * }
9890
+ * ```
9891
+ */
9892
+ async getWalletLoanEvents(userAddress, filter) {
9893
+ if (this.config.debug) {
9894
+ log.info("\u{1F50D} Querying wallet loan events", { userAddress, filter });
9895
+ }
9896
+ return tryCatchAsync(
9897
+ async () => {
9898
+ const perPosition = await this.config.graphClient.getWalletLoanEvents(
9899
+ userAddress,
9900
+ filter
9901
+ );
9902
+ if (this.config.debug) {
9903
+ log.info("\u2705 Retrieved wallet loan events", {
9904
+ userAddress,
9905
+ positions: perPosition.length,
9906
+ totalEvents: perPosition.reduce((n, p) => n + p.totalEvents, 0)
9907
+ });
9908
+ }
9909
+ return perPosition;
9910
+ },
9911
+ (error) => new SDKError({
9912
+ message: "Failed to query wallet loan events",
9913
+ category: "SUBGRAPH" /* SUBGRAPH */,
9914
+ severity: "MEDIUM" /* MEDIUM */,
9915
+ originalError: error instanceof Error ? error : new Error(String(error)),
9916
+ context: { userAddress, filter }
9917
+ })
9918
+ );
9919
+ }
8850
9920
  /**
8851
9921
  * Clear loan cache
8852
9922
  *
@@ -8889,7 +9959,7 @@ function createLoanQuery(config) {
8889
9959
  }
8890
9960
 
8891
9961
  // src/modules/withdrawal-address/withdrawal-address.module.ts
8892
- var import_ethers11 = require("ethers");
9962
+ var import_ethers12 = require("ethers");
8893
9963
  var WithdrawalAddressModule = class {
8894
9964
  config;
8895
9965
  graphClient;
@@ -8906,7 +9976,7 @@ var WithdrawalAddressModule = class {
8906
9976
  if (!res.success)
8907
9977
  return res;
8908
9978
  const target = res.value.target;
8909
- if (!target || target === import_ethers11.ZeroAddress) {
9979
+ if (!target || target === import_ethers12.ZeroAddress) {
8910
9980
  return failure(
8911
9981
  new SDKError({
8912
9982
  code: "SDK_WITHDRAWAL_REGISTRY_NOT_CONFIGURED",
@@ -8924,7 +9994,7 @@ var WithdrawalAddressModule = class {
8924
9994
  }
8925
9995
  /** keccak256 of the canonicalized address — matches the on-chain key. */
8926
9996
  static addressHash(btcAddress) {
8927
- return (0, import_ethers11.keccak256)((0, import_ethers11.toUtf8Bytes)(normalizeBitcoinAddress(btcAddress)));
9997
+ return (0, import_ethers12.keccak256)((0, import_ethers12.toUtf8Bytes)(normalizeBitcoinAddress(btcAddress)));
8928
9998
  }
8929
9999
  /**
8930
10000
  * Add a Bitcoin address to the caller's allowlist. It becomes usable only
@@ -11588,6 +12658,39 @@ var DiamondHandsGraph = class {
11588
12658
  totalEvents
11589
12659
  };
11590
12660
  }
12661
+ /**
12662
+ * Get the full loan-event history for every position owned by a wallet.
12663
+ *
12664
+ * The subgraph has no wallet-scoped event collection (`User` only derives
12665
+ * `positions`), so this composes the two existing wallet/position queries:
12666
+ * `getUserPositions(wallet)` to enumerate the wallet's positions, then
12667
+ * `getLoanEvents(positionId, filter)` per position. Each returned `LoanEvents`
12668
+ * already carries its own `positionId`, so callers can flatten and merge into
12669
+ * a single timeline.
12670
+ *
12671
+ * Note: Bitcoin deposits are UTXOs on the Bitcoin network and never appear in
12672
+ * the EVM subgraph — surface those from an Esplora source and merge separately.
12673
+ *
12674
+ * @param userAddress - The loan-owner wallet address.
12675
+ * @param filter - Optional per-position event filter (same semantics as getLoanEvents).
12676
+ * @returns One LoanEvents object per position; empty array if the wallet has none.
12677
+ */
12678
+ async getWalletLoanEvents(userAddress, filter) {
12679
+ const { positions } = await this.getUserPositions(userAddress);
12680
+ const positionIds = positions.map((p) => p?.id != null ? String(p.id) : "").filter((id) => id.length > 0);
12681
+ if (positionIds.length === 0)
12682
+ return [];
12683
+ const CONCURRENCY = 5;
12684
+ const results = [];
12685
+ for (let i = 0; i < positionIds.length; i += CONCURRENCY) {
12686
+ const batch = positionIds.slice(i, i + CONCURRENCY);
12687
+ const settled = await Promise.all(
12688
+ batch.map((positionId) => this.getLoanEvents(positionId, filter))
12689
+ );
12690
+ results.push(...settled);
12691
+ }
12692
+ return results;
12693
+ }
11591
12694
  // ============================================================================
11592
12695
  // Protocol-wide Events
11593
12696
  // ============================================================================
@@ -11937,7 +13040,7 @@ var DiamondHandsGraph = class {
11937
13040
  };
11938
13041
 
11939
13042
  // src/contracts/typechain-contracts/factories/src/psm/SimplePSMV2__factory.ts
11940
- var import_ethers12 = require("ethers");
13043
+ var import_ethers13 = require("ethers");
11941
13044
  var _abi = [
11942
13045
  {
11943
13046
  inputs: [],
@@ -14505,7 +15608,7 @@ var _abi = [
14505
15608
  ];
14506
15609
  var _bytecode = "0x60a080604052346100ea57306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166100d9576002600160401b03196001600160401b03821601610073575b60405161485a90816100f0823960805181818161278e01526128870152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a13880610054565b63f92ee8a960e01b60005260046000fd5b600080fdfe6080604052600436101561001257600080fd5b6000803560e01c8062f7fce91461384657806301ffc9a7146137d5578063021d28931461377c5780630871b20614613743578063114249d214610f6157806311d954f81461371a57806313da3166146136e15780631d44a904146136385780631f77bf65146135ff578063248a9ca3146135e05780632b83cccd146131205780632f2ff15d146130ee57806334bac370146130b557806334e181251461307c57806336568abe14613037578063373c86b614612ffe5780633f4ba83a14612f375780634373b57214612f0e5780634451e89b14612b3a5780634ebd25a514612b015780634f1ef2861461280c5780635273b6f6146127e357806352d1902d1461277b578063568fafca146126ed57806359d5d02a146124205780635bc26aea146123eb5780635c975abb146123bb5780635ecc27081461229d5780638456cb59146121a257806385c15d9a1461216957806389d3ad00146120a25780638bff608e146120695780639010d07c1461201657806390ad8b4014611ffa57806391d1485414611fa15780639b759bf714611f5e5780639baf58d214611f415780639f1d0f59146118a8578063a217fddf1461188c578063a3246ad3146117cf578063ad3cb1cc14611788578063b492fc691461168e578063b9f637af14611463578063bf7d911914611424578063c4c5f62414611371578063c55f15ea1461134e578063ca15c87314611317578063ca2ce65d146112de578063cc1f8ffa146112ac578063cf46075e146111a2578063d21a74ff14611165578063d2f8253614611142578063d547741f14611107578063d5d3f78b14610f9a578063d66bd52414610f61578063e1f1c4a714610f44578063e1f3962c14610f18578063ec2f96fe14610ef4578063eda5a26a14610af7578063f1a1eb8414610ab4578063f385116d14610a7b578063f77c479114610a54578063f8c8765e14610661578063faaebd2114610628578063facdfb5e14610488578063fc99bd9a14610377578063fe32e7281461033e5763ffa1ad741461030857600080fd5b3461033b578060031936011261033b57610337610323613a68565b604051918291602083526020830190613a17565b0390f35b80fd5b503461033b57602036600319011261033b576020906040906001600160a01b0361036661392e565b168152601283522054604051908152f35b503461033b57604036600319011261033b5761039161392e565b6010546024359291906001600160a01b03163303610479576001600160a01b03168082526002602052604082205490929060ff16156104665780156104575782825260076020526040822054926103e88285613cb0565b848110610448576001600160ff1b0381116104395760008051602061482e833981519152604085968493846104369852600760205280838a205582519182526020820152a23090339061421c565b80f35b630700fcb360e11b8452600484fd5b63446c8cc960e01b8452600484fd5b63162908e360e11b8252600482fd5b506024916374ed46ad60e01b8252600452fd5b6349db3bd360e01b8252600482fd5b503461033b57602036600319011261033b576104a261392e565b6104aa613f12565b6010546001600160a01b03163303610479576001600160a01b03168082526002602052604082205460ff161561061657808252600760205260408220546040516370a0823160e01b8152306004820152602081602481865afa90811561060b5784916105d4575b50818111156105c5576105248282613b6c565b8215908115610570575b505060008051602061482e833981519152916040918486526007602052808387205582519182526020820152a260016000805160206147ce8339815191525580f35b61271081029080820461271014901517156105b157606484029184830460641417156105b157116105a257388061052e565b63a73af74d60e01b8452600484fd5b634e487b7160e01b86526011600452602486fd5b6105cf8183613b6c565b610524565b90506020813d602011610603575b816105ef602093836139c3565b810103126105fe575138610511565b600080fd5b3d91506105e2565b6040513d86823e3d90fd5b6374ed46ad60e01b8252600452602490fd5b503461033b57602036600319011261033b576020906040906001600160a01b0361065061392e565b168152600483522054604051908152f35b503461033b57608036600319011261033b5761067b61392e565b610683613944565b6044356001600160a01b03811690818103610a50576064356001600160a01b0381169390849003610a4c5760008051602061480e83398151915254604081901c60ff161595906001600160401b031680159081610a44575b6001149081610a3a575b159081610a31575b50610a225760008051602061480e83398151915280546001600160401b0319166001179055856109fd575b6001600160a01b03169081156109ee576001600160a01b03169081156109df5783156109d05761074661448a565b61074e61448a565b61075661448a565b61075e61448a565b60016000805160206147ce8339815191525561077861448a565b61078061448a565b86546001600160a01b0319908116919091178755600180549182168317905560405163313ce56760e01b8152909190602081600481855afa88918161099f575b506107d457633a24e95b60e11b8852600488fd5b6001600160a81b03199092161760a091821b60ff60a01b16176001819055901c60ff16601119810161098d575061080b8186614348565b80610965575b156109205761082e816000805160206147ee833981519152614348565b8061092f575b15610920576108519060008051602061470e833981519152614348565b90816108e9575b50156108da57806108af575b5061086c5780f35b60ff60401b1960008051602061480e833981519152541660008051602061480e833981519152556000805160206146ee833981519152602060405160018152a180f35b601080546001600160a01b031916821790558260008051602061474e8339815191528180a338610864565b63cf5a8e3360e01b8352600483fd5b6109199060008051602061470e83398151915286526000805160206146ae833981519152602052604086206144d0565b5038610858565b63cf5a8e3360e01b8552600485fd5b6000805160206147ee83398151915286526000805160206146ae83398151915260205261095f83604088206144d0565b50610834565b8580526000805160206146ae83398151915260205261098783604088206144d0565b50610811565b63167738cf60e21b8652600452602485fd5b6109c291925060203d6020116109c9575b6109ba81836139c3565b810190613ba9565b90386107c0565b503d6109b0565b630b5eba9f60e41b8752600487fd5b63c1ab6dc160e01b8752600487fd5b6336abb4df60e11b8752600487fd5b60008051602061480e833981519152805460ff60401b1916600160401b179055610718565b63f92ee8a960e01b8752600487fd5b905015386106ed565b303b1591506106e5565b8791506106db565b8580fd5b8480fd5b503461033b578060031936011261033b57546040516001600160a01b039091168152602090f35b503461033b57602036600319011261033b576020906040906001600160a01b03610aa361392e565b168152600a83522054604051908152f35b503461033b57604036600319011261033b576020906040906001600160a01b03610adc61392e565b16815260158352818120602435825283522054604051908152f35b503461033b5761016036600319011261033b57610b1261392e565b90606435604435602435610b24613a58565b9160a4359160c43560e43561010435916101243595610144359760018060a01b03601054163303610ee5576001600160a01b038c16808c52600260205260408c2054909a9060ff16610ed1578a15610ec2578615610eb3576127108811610e99576103e88111610e815760ff82169c60128e118e8115610e76575b50610e6257803b15610e4e578915610e3a578a15610e265782610bc191614260565b84151580610e1d575b610e065785151580610dfd575b610de6578b9c602460208d9e9d604051928380926370a0823160e01b82523060048301525afa9c8d15610dda579c610da6575b506001600160ff1b038c11610d97578a8d8f9760408f97838f918f8f8f8f90859f988f99879f9660008051602061476e8339815191529f978b9f988f8d6000805160206146ce8339815191529f988e8e610d4f9b60008051602061468e8339815191529f8f8f8487526002602052878720600160ff198254161790558487526003602052878720558386526004602052868620558285526006602052878686205582855260056020528585209060ff19825416179055818452600c6020528885852055818452600d6020528985852055818452600e6020528a85852055818452600f6020528b8585205581845260076020528484205582526012602052828220558d8152601360205220558a7f991d1ef1b5c0820d213328cba6682059e00a50e49c469a454eb3557e78eaf6668e80518b81528c6020820152a28c5198899889613cbd565b0390a28151908b82526020820152a28151908782526020820152a280610d73578280f35b604060008051602061482e833981519152918151908582526020820152a281808280f35b63556e7a8160e01b8e5260048efd5b909b506020813d602011610dd2575b81610dc2602093836139c3565b810103126105fe57519a38610c0a565b3d9150610db5565b604051903d90823e3d90fd5b60448c858863dd85772b60e01b8352600452602452fd5b50838610610bd7565b60448c8487637449915760e01b8352600452602452fd5b50828510610bca565b636835314760e11b8d52600160045260248dfd5b636835314760e11b8d5260048d905260248dfd5b63377e3f9960e21b8d5260048c905260248dfd5b63ca95039160e01b8d5260048e905260248dfd5b60069150108e610b9f565b631051834f60e11b8c526004526103e860245260448bfd5b63c1e9a14160e01b8c52600488905261271060245260448cfd5b63a2ec3a5560e01b8c5260048cfd5b63df407ac560e01b8c5260048cfd5b6317f4215b60e21b8c5260048b905260248cfd5b6349db3bd360e01b8b5260048bfd5b503461033b578060031936011261033b57602060ff60015460a01c16604051908152f35b503461033b57602036600319011261033b576020610f3c610f3761392e565b613df5565b604051908152f35b503461033b578060031936011261033b5760206040516127108152f35b503461033b57602036600319011261033b576020906040906001600160a01b03610f8961392e565b168152600783522054604051908152f35b503461033b57602036600319011261033b57610fb461392e565b6010546001600160a01b03163303610479576001600160a01b03168082526002602052604082205460ff16156106165780825260076020526040822054806110f1575080825260026020526040822060ff1981541690558082526003602052816040812055808252600460205281604081205580825260056020526040822060ff198154169055808252600c602052816040812055808252600d602052816040812055808252600e602052816040812055808252600f602052816040812055808252600660205281604081205580825260076020526040822054818352600760205282604081205560405190827f3302a1b37f04242bb55603f5449e7ecd690a35eef08ba1353c48c20e2aae9cda8580a2806110ce578380f35b8160409160008051602061482e8339815191529352846020820152a23880808380f35b604492916376bc059760e11b8352600452602452fd5b503461033b57604036600319011261033b5761113e600435611127613944565b9061113961113482613b79565b614088565b61411a565b5080f35b503461033b578060031936011261033b57602060ff600854166040519015158152f35b503461033b57602036600319011261033b5760209060ff906040906001600160a01b0361119061392e565b16815260058452205416604051908152f35b503461033b57602036600319011261033b576111bc61392e565b6111c4613f12565b6010546001600160a01b03163303610479576001600160a01b03168082526002602052604082205460ff161561061657808252600760205260408220546040516370a0823160e01b8152306004820152602081602481865afa90811561060b57849161126c575b5060008051602061482e833981519152916040918486526007602052808387205582519182526020820152a260016000805160206147ce8339815191525580f35b90506020813d6020116112a4575b81611287602093836139c3565b810103126105fe575160008051602061482e83398151915261122b565b3d915061127a565b503461033b57604036600319011261033b5760206112d46112cb61392e565b60243590613cf6565b6040519015158152f35b503461033b57602036600319011261033b576020906040906001600160a01b0361130661392e565b168152601383522054604051908152f35b503461033b57602036600319011261033b57604060209160043581526000805160206146ae83398151915283522054604051908152f35b503461033b578060031936011261033b576020604051670de0b6b3a76400008152f35b503461033b57604036600319011261033b5761138b61392e565b6010546024359291906001600160a01b03163303610479576001600160a01b031680825260076020526040822054929081841061140d578060008051602061482e8339815191526040866113e3866104369899613b6c565b84895260076020528289205583885260076020528188205482519182526020820152a2339061404c565b5091604492633cb8e07360e01b8352600452602452fd5b503461033b57602036600319011261033b5760209060ff906040906001600160a01b0361144f61392e565b168152600284522054166040519015158152f35b503461033b5761012036600319011261033b5761147e61392e565b6064359190604435602435611491613a58565b601054610104359660e4359260c4359260a43592906001600160a01b0316330361167f576001600160a01b038816808a52600260205260408a205490989060ff161561166b57861561165c576127108811611642576103e882116116285760ff83169060128211801561161e575b61160a578361150d91614260565b85151580611601575b6115ea578a1515806115e1575b6115ca579260008051602061468e83398151915297969592828c956115c4979460408e8e80825260036020528c8383205580825260046020528d838320558152600660205220558b8d52600560205260408d209060ff198254161790558a8c52600c6020528260408d20558a8c52600d6020528360408d20558a8c52600e6020528460408d20558a8c52600f6020528560408d205560405198899889613cbd565b0390a280f35b60448a868d63dd85772b60e01b8352600452602452fd5b50848b10611523565b60448a8588637449915760e01b8352600452602452fd5b50838610611516565b63ca95039160e01b8b52600482905260248bfd5b50600682106114ff565b631051834f60e11b8a5260048290526103e860245260448afd5b63c1e9a14160e01b8a52600488905261271060245260448afd5b63a2ec3a5560e01b8a5260048afd5b6374ed46ad60e01b8a52600489905260248afd5b6349db3bd360e01b8952600489fd5b503461033b57604036600319011261033b576116a861392e565b6001600160a01b0316808252600260205260408220546024359060ff16156117745780156117655761271061172a604085670de0b6b3a76400006117196117329688859a52600560205260ff85852054169080916012811061173f575b505088845260036020528484205490613a8b565b049581526004602052205484613a8b565b048092613b6c565b9082519182526020820152f35b61175e925060ff61175261175892613ad4565b16613ae5565b90613a8b565b3880611705565b63162908e360e11b8352600483fd5b6374ed46ad60e01b83526004829052602483fd5b503461033b578060031936011261033b5761033760408051906117ab81836139c3565b60058252640352e302e360dc1b602083015251918291602083526020830190613a17565b503461033b57602036600319011261033b5760043581526000805160206146ae83398151915260205260408120604051908160208254918281520190819285526020852090855b818110611876575050508261182c9103836139c3565b604051928392602084019060208552518091526040840192915b818110611854575050500390f35b82516001600160a01b0316845285945060209384019390920191600101611846565b8254845260209093019260019283019201611816565b503461033b578060031936011261033b57602090604051908152f35b503461033b576118b736613969565b9190926118c2613f12565b6118ca613f4e565b338152600960205260408120544303611f1f57338152600a602052600360408220541015611f1057338152600a602052604081206119088154613b9a565b90555b6001600160a01b0382168082526002602052604082205490929060ff16611e6f575b828252600260205260ff60408320541615610466578415610457578315611e6057828252600c60205260408220548510611e3b57828252600e60205260408220548015611e2c57808611611e155750846119869161415e565b6040516370a0823160e01b8152306004820152602081602481865afa908115611e0a578291611dd8575b506119bd8530338661421c565b6040516370a0823160e01b8152306004820152602081602481875afa908115611cef57908692918491611d9f575b50906119f691613b6c565b10611d905781815260076020526040812054611a128582613cb0565b818110611d81576001600160ff1b038111611d72576040849260008051602061482e833981519152928486526007602052808387205582519182526020820152a2818152600560205260ff604082205416849060128110611d11575b50611a8f670de0b6b3a7640000918484526003602052604084205490613a8b565b04908281526004602052612710611aaa604083205484613a8b565b0491611ab68382613b6c565b94808610611cfa57506001546040516318160ddd60e01b815290602090829060049082906001600160a01b03165afa908115611cef578391611cbd575b5082546001600160a01b0316803b15611ca45783809160846040518094819363ae494fa160e01b83523360048401528c60248401528b60448401528d60648401525af1801561060b57908491611ca8575b505083611c34575b6001546040516318160ddd60e01b81529190602090839060049082906001600160a01b03165afa801561060b578490611c00575b611b8a9250613b6c565b90808203611bea575050507fb3e2773606abfd36b5bd91394b3a54d1398336c65005baf7bf7a05efeffaf75b611bcd602095604051918291339588429285613bdd565b0390a360016000805160206147ce83398151915255604051908152f35b632d8562a360e11b835260045260245260449150fd5b506020823d602011611c2c575b81611c1a602093836139c3565b810103126105fe57611b8a9151611b80565b3d9150611c0d565b82546001600160a01b0316803b15611ca45783809160846040518094819363ae494fa160e01b83523060048401528a60248401528b60448401528160648401525af1801561060b57908491611c8b575b5050611b4c565b81611c95916139c3565b611ca0578238611c84565b8280fd5b8380fd5b81611cb2916139c3565b611ca0578238611b44565b90506020813d602011611ce7575b81611cd8602093836139c3565b810103126105fe575138611af3565b3d9150611ccb565b6040513d85823e3d90fd5b6371c4efed60e01b83526004869052602452604482fd5b611d21915061175260ff91613ad4565b8015611d5e5780600019048511611d4f57611a8f611d48670de0b6b3a76400009287613a8b565b9150611a6e565b63035fc53d60e41b8252600482fd5b634e487b7160e01b82526012600452602482fd5b630700fcb360e11b8352600483fd5b63446c8cc960e01b8352600483fd5b639bfa3c1760e01b8152600490fd5b919250506020813d602011611dd0575b81611dbc602093836139c3565b810103126105fe57518591906119f66119eb565b3d9150611daf565b90506020813d602011611e02575b81611df3602093836139c3565b810103126105fe5751386119b0565b3d9150611de6565b6040513d84823e3d90fd5b631d7f2ac360e01b83526004869052602452604482fd5b633b5db2f360e11b8352600483fd5b50908152600c6020526040812054630d54e9e160e01b82526004849052602452604490fd5b632cd84af360e01b8252600482fd5b60405163313ce56760e01b815291939291602081600481865afa859181611eef575b50611ea55763105482c760e21b8552600485fd5b93919293838352600560205260ff806040852054169116908103611ec9575061192d565b90506044928252600560205260ff604083205416631e43476360e11b8352600452602452fd5b611f0991925060203d6020116109c9576109ba81836139c3565b9038611e91565b63a74c1c5f60e01b8152600490fd5b3381526009602052436040822055338152600a6020526001604082205561190b565b503461033b578060031936011261033b5760206040516103e88152f35b503461033b57604036600319011261033b576020906040906001600160a01b03611f8661392e565b16815260148352818120602435825283522054604051908152f35b503461033b57604036600319011261033b576040611fbd613944565b91600435815260008051602061478e833981519152602052209060018060a01b0316600052602052602060ff604060002054166040519015158152f35b503461033b578060031936011261033b57602060405160648152f35b503461033b57604036600319011261033b5761205060209160043581526000805160206146ae8339815191528352604060243591206144b8565b905460405160039290921b1c6001600160a01b03168152f35b503461033b57602036600319011261033b576020906040906001600160a01b0361209161392e565b168152600983522054604051908152f35b503461033b57604036600319011261033b576120bc61392e565b60105460243591906001600160a01b0316330361215a576001600160a01b031690811561214b57604060008051602061476e8339815191529183855260136020528185205490848652601360205280838720558486526015602052828620438752602052828620548587526019602052838720558486526017602052438387205582519182526020820152a280f35b63df407ac560e01b8352600483fd5b6349db3bd360e01b8352600483fd5b503461033b57602036600319011261033b576020906040906001600160a01b0361219161392e565b168152600383522054604051908152f35b503461033b578060031936011261033b5760008051602061470e833981519152815260008051602061478e83398151915260209081526040808320336000908152925290205460ff1615612278576121f8613f4e565b600160ff196000805160206147ae8339815191525416176000805160206147ae833981519152557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a16040514281527f80170b5fcdd2bf1e0660ef4b8851f86685f64d41b1a19de1471947ece8725aac60203392a280f35b63e2517d3f60e01b81523360045260008051602061470e833981519152602452604490fd5b503461033b57602036600319011261033b576122b761392e565b60008051602061480e8339815191525460ff8160401c169081156123a3575b506123945760008051602061480e83398151915280546001600160481b0319166003600160401b0117905581805260008051602061478e83398151915260209081526040808420336000908152925290205460ff161561237c5761233990613c5e565b60ff60401b1960008051602061480e833981519152541660008051602061480e833981519152556000805160206146ee833981519152602060405160038152a180f35b63e2517d3f60e01b8252336004526024829052604482fd5b63f92ee8a960e01b8252600482fd5b60036001600160401b039190911610159050386122d6565b503461033b578060031936011261033b57602060ff6000805160206147ae83398151915254166040519015158152f35b503461033b57602036600319011261033b5761240561392e565b6010546001600160a01b031633036104795761043690613c5e565b503461033b57606036600319011261033b5761243a61392e565b612442613944565b60105490926044359290916001600160a01b0316330361047957612464613f4e565b6011546001600160a01b03169182156126de5760405163babcc53960e01b81526001600160a01b0386166004820181905293602090829060249082905afa908115611e0a57829161269f575b5015612690578215612681576001546001600160a01b03928316921682036125d1576040516370a0823160e01b8152306004820152602081602481865afa908115611e0a57829161259f575b50935b841561259057600019810361258a5750835b841561257b5780851161256357505061252d836020958361404c565b7f0bf519e4b3821615dbdd874fa1a5fbb4bb651b4e6beabedd4ac318e770233b2c604080518581524287820152a3604051908152f35b630555ed4f60e31b8252600485905260245260449150fd5b632e11316f60e11b8252600482fd5b93612511565b63f09872eb60e01b8252600482fd5b90506020813d6020116125c9575b816125ba602093836139c3565b810103126105fe5751386124fc565b3d91506125ad565b8181526002602052604081205460ff161561266f576040516370a0823160e01b8152306004820152602081602481865afa908115611e0a57829161263d575b5082825260076020526040822054808211156126355761262f91613b6c565b936124ff565b50508061262f565b90506020813d602011612667575b81612658602093836139c3565b810103126105fe575138612610565b3d915061264b565b60249163eef7849760e01b8252600452fd5b634e46966960e11b8152600490fd5b63133307bb60e21b8152600490fd5b90506020813d6020116126d6575b816126ba602093836139c3565b810103126126d2575180151581036126d257386124b0565b5080fd5b3d91506126ad565b630e048e7160e41b8152600490fd5b503461033b57602036600319011261033b5761270761392e565b6010546001600160a01b03163303610479576001600160a01b0316801561276c57601180546001600160a01b0319811683179091556001600160a01b03167f1e9c406f2707909cfd9f61e744f73bcab77e5997d063276f67883c5257d51cf08380a380f35b634e46966960e11b8252600482fd5b503461033b578060031936011261033b577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036127d457602060405160008051602061472e8339815191528152f35b63703e46dd60e11b8152600490fd5b503461033b578060031936011261033b576010546040516001600160a01b039091168152602090f35b50604036600319011261033b5761282161392e565b602435906001600160401b038211611ca05736602383011215611ca0578160040135908361284e836139fc565b9361285c60405195866139c3565b83855260208501933660248284010111611ca057806024602093018637850101526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115612ade575b50612acf576010546001600160a01b03163303612ac0576001600160a01b038116928315612ab157813b15612aa25760008051602061472e83398151915254846128fa613a68565b7f6ea8b34d59b2818d3f37a08dddaa06f5886b9c20c907b36f03ca6528e106e86f61293060405192604084526040840190613a17565b4260208401526001600160a01b038516929081900390a36040516352d1902d60e01b8152602081600481895afa879181612a6a575b5061297e57634c9c8ce360e01b87526004869052602487fd5b908160008051602061472e83398151915288979303612a585750833b15612a44576001600160a01b031916811760008051602061472e833981519152558491907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8380a2805115612a295761113e9382915190845af43d15612a21573d91612a05836139fc565b92612a1360405194856139c3565b83523d85602085013e61460c565b60609161460c565b5050505034612a355780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b86526004829052602486fd5b632a87526960e21b8752600452602486fd5b9091506020813d602011612a9a575b81612a86602093836139c3565b81010312612a9657519038612965565b8780fd5b3d9150612a79565b636f7c43f160e01b8552600485fd5b63e6c4247b60e01b8552600485fd5b6349db3bd360e01b8452600484fd5b63703e46dd60e11b8452600484fd5b60008051602061472e833981519152546001600160a01b031614159050386128b2565b503461033b57602036600319011261033b576020906040906001600160a01b03612b2961392e565b168152600e83522054604051908152f35b503461033b57606036600319011261033b57612b5461395a565b6024356001600160401b038111611ca057612b73903690600401613993565b916044356001600160401b038111610a5057612b93903690600401613993565b60008051602061480e8339815191529291925460ff8160401c16908115612ef6575b50612ee75760008051602061480e83398151915280546001600160481b0319166002600160401b011790556000805160206147ee833981519152865260008051602061478e83398151915260209081526040808820336000908152925290205460ff1615612ec257612c2561448a565b612c2d61448a565b60016000805160206147ce83398151915255600b549160ff8316612eb357818603612ea457612c5b86613bf9565b92612c6960405194856139c3565b868452601f19612c7888613bf9565b01366020860137875b878110612daf575060019060ff191617600b5515159460ff196008541660ff871617600855865b818110612d0c578760008051602061466e833981519152602089604051908152a160ff60401b1960008051602061480e833981519152541660008051602061480e833981519152556000805160206146ee833981519152602060405160028152a180f35b808860008051602061482e8339815191526040612d34612d2f600196888d613c10565b613c36565b612d3f85898c613c10565b357f8f955fa878b4aae8b1ac37b1ab715307470f2822ceff87e23d33ef1e044814a783612d6c888d613c4a565b5193898060a01b0316968793848252600660205280838320558482526007602052858383205582519182526020820152a28151908d82526020820152a201612ca8565b612dbd612d2f828a8a613c10565b612dc8828689613c10565b6001600160a01b03909116808b52600260205260408b20549091359060ff1615612e90576103e88111612e785750906020602492604051938480926370a0823160e01b82523060048301525afa8015612e6d578a90612e37575b60019250612e308288613c4a565b5201612c81565b509060203d8111612e66575b612e4d81836139c3565b6020826000928101031261033b57509060019151612e22565b503d612e43565b6040513d8c823e3d90fd5b631051834f60e11b8b526004526103e860245260448afd5b6351104c5960e01b8b52600482905260248bfd5b63512509d360e11b8752600487fd5b6324d0d1fd60e01b8752600487fd5b63e2517d3f60e01b8652336004526000805160206147ee833981519152602452604486fd5b63f92ee8a960e01b8652600486fd5b60026001600160401b03919091161015905038612bb5565b503461033b578060031936011261033b576011546040516001600160a01b039091168152602090f35b503461033b578060031936011261033b576010546001600160a01b03163303612fef576000805160206147ae8339815191525460ff811615612fe05760ff19166000805160206147ae833981519152557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a16040514281527f107553d8191d85b405879cf752997865edd48d94e20bda4dd27223c94b31a7cc60203392a280f35b638dfc202b60e01b8252600482fd5b6349db3bd360e01b8152600490fd5b503461033b57602036600319011261033b576020906040906001600160a01b0361302661392e565b168152601683522054604051908152f35b503461033b57604036600319011261033b57613051613944565b336001600160a01b0382160361306d5761113e9060043561411a565b63334bd91960e11b8252600482fd5b503461033b57602036600319011261033b576020906040906001600160a01b036130a461392e565b168152600c83522054604051908152f35b503461033b57602036600319011261033b576020906040906001600160a01b036130dd61392e565b168152601783522054604051908152f35b503461033b57604036600319011261033b5761113e60043561310e613944565b9061311b61113482613b79565b6140d2565b503461033b5761312f36613969565b91909261313a613f12565b613142613f4e565b3381526009602052604081205443036135be57338152600a602052600360408220541015611f1057338152600a602052604081206131808154613b9a565b90555b6001600160a01b0382168082526002602052604082205490929060ff16613543575b828252600260205260ff604083205416156104665760ff6008541615613534578415610457578315611e6057828252600d602052604082205480861061351d5750828252600f6020526040822054801561350e578086116134f857508461320b91613f78565b670de0b6b3a76400008402848104670de0b6b3a7640000036134e4578282526003602052604082205461323d91613ab4565b90828152600560205260ff604082205416601281106134c4575b50828152600660205261327c612710613274604084205485613a8b565b048093613b6c565b938085106134ac575082815260076020528360408220541061348857828152600760205260408120548481106134795760008051602061482e8339815191526040826132c9888895613b6c565b8486526007602052808387205582519182526020820152a26001546040516318160ddd60e01b815290602090829060049082906001600160a01b03165afa908115611e0a578291613447575b5081546001600160a01b0316803b15611ca057826040518092633a52b90760e11b82528183816133498d3360048401613bc2565b03925af18015611cef57613432575b506001546040516318160ddd60e01b81529190602090839060049082906001600160a01b03165afa908115611cef5783916133fc575b6133989250613b6c565b8581036133e45750507f8caf04742286d017f9ac3924388e188c73e6e5094311c5e59a61a7ef86dda8bf611bcd6020956133d386338761404c565b604051918291339588429285613bdd565b635ab6e2db60e01b8252600486905260245260449150fd5b90506020823d60201161342a575b81613417602093836139c3565b810103126105fe5761339891519061338e565b3d915061340a565b9161344081600493946139c3565b9190613358565b90506020813d602011613471575b81613462602093836139c3565b810103126126d2575138613315565b3d9150613455565b6308aed4ef60e31b8252600482fd5b82815260076020526040812054635960d22160e01b82526004526024849052604490fd5b6371c4efed60e01b8252600485905260245260449150fd5b916134d760ff6117526134dd9495613ad4565b90613ab4565b9038613257565b634e487b7160e01b82526011600452602482fd5b625502a360e91b83526004869052602452604482fd5b636c6ff70d60e11b8352600483fd5b6361767b8560e01b83526004869052602452604482fd5b630df65d9f60e01b8252600482fd5b60405163313ce56760e01b815291939291602081600481865afa85918161359d575b506135795763105482c760e21b8552600485fd5b93919293838352600560205260ff806040852054169116908103611ec957506131a5565b6135b791925060203d6020116109c9576109ba81836139c3565b9038613565565b3381526009602052436040822055338152600a60205260016040822055613183565b503461033b57602036600319011261033b576020610f3c600435613b79565b503461033b57602036600319011261033b576020906040906001600160a01b0361362761392e565b168152600f83522054604051908152f35b503461033b57604036600319011261033b5761365261392e565b60105460243591906001600160a01b0316330361215a576001600160a01b031690811561214b5760406000805160206146ce8339815191529183855260126020528185205490848652601260205280838720558486526014602052828620438752602052828620548587526018602052838720558486526016602052438387205582519182526020820152a280f35b503461033b57602036600319011261033b576020906040906001600160a01b0361370961392e565b168152600683522054604051908152f35b503461033b578060031936011261033b576001546040516001600160a01b039091168152602090f35b503461033b57602036600319011261033b576020906040906001600160a01b0361376b61392e565b168152600d83522054604051908152f35b503461033b57602036600319011261033b5761379661395a565b6010546001600160a01b0316330361047957602060008051602061466e83398151915291151560ff196008541660ff821617600855604051908152a180f35b503461033b57602036600319011261033b5760043563ffffffff60e01b81168091036126d257602090635a05180f60e01b811490811561381b575b506040519015158152f35b637965db0b60e01b811491508115613835575b5082613810565b6301ffc9a760e01b1490508261382e565b503461033b57604036600319011261033b5761386061392e565b6001600160a01b0316808252600260205260408220546024359060ff161561177457670de0b6b3a7640000810290808204670de0b6b3a7640000149015171561391a57916138bc60409383835260036020528483205490613ab4565b91808252600560205260ff8483205416601281106138f1575b5061172a84836127109361173295526006602052205484613a8b565b846117329361390f61271094966134d760ff61175261172a97613ad4565b9593509350506138d5565b634e487b7160e01b83526011600452602483fd5b600435906001600160a01b03821682036105fe57565b602435906001600160a01b03821682036105fe57565b6004359081151582036105fe57565b60609060031901126105fe576004356001600160a01b03811681036105fe57906024359060443590565b9181601f840112156105fe578235916001600160401b0383116105fe576020808501948460051b0101116105fe57565b601f909101601f19168101906001600160401b038211908210176139e657604052565b634e487b7160e01b600052604160045260246000fd5b6001600160401b0381116139e657601f01601f191660200190565b919082519283825260005b848110613a43575050826000602080949584010152601f8019910116010190565b80602080928401015182828601015201613a22565b6084359060ff821682036105fe57565b60405190613a776040836139c3565b6005825264322e332e3360d81b6020830152565b81810292918115918404141715613a9e57565b634e487b7160e01b600052601160045260246000fd5b8115613abe570490565b634e487b7160e01b600052601260045260246000fd5b60ff166012039060ff8211613a9e57565b8015613b665760009060208110600116604e821060011617613b5e57600a916001915b60018111613b35575082600019048211613b2157500290565b634e487b7160e01b81526011600452602490fd5b92806000190481116134e45760018416613b55575b80029260011c613b08565b80920291613b4a565b9050600a0a90565b50600190565b91908203918211613a9e57565b60005260008051602061478e83398151915260205260016040600020015490565b6000198114613a9e5760010190565b908160209103126105fe575160ff811681036105fe5790565b6001600160a01b039091168152602081019190915260400190565b9094939260609260808301968352602083015260408201520152565b6001600160401b0381116139e65760051b60200190565b9190811015613c205760051b0190565b634e487b7160e01b600052603260045260246000fd5b356001600160a01b03811681036105fe5790565b8051821015613c205760209160051b010190565b6001600160a01b03168015613c9f57601080546001600160a01b0319811683179091556001600160a01b031660008051602061474e833981519152600080a3565b63e6c4247b60e01b60005260046000fd5b91908201809211613a9e57565b94919360e0969360ff929a99989561010088019b885260208801526040870152166060850152608084015260a083015260c08201520152565b6001600160a01b03811660008181526002602052604090205490919060ff16158015613de8575b8015613de0575b613dce5781600052600f6020526040600020548015908115613dd6575b50613dce57613d66926040918251948592839262f7fce960e01b845260048401613bc2565b0381305afa918215613dc257600092613d8e575b506000526007602052604060002054101590565b9091506040813d604011613dba575b81613daa604093836139c3565b810103126105fe57519038613d7a565b3d9150613d9d565b6040513d6000823e3d90fd5b505050600090565b9050831138613d41565b508215613d24565b5060ff6008541615613d1d565b6001546001600160a01b0391821691168103613e6a576020602491604051928380926370a0823160e01b82523060048301525afa908115613dc257600091613e3b575090565b90506020813d602011613e62575b81613e56602093836139c3565b810103126105fe575190565b3d9150613e49565b80600052600260205260ff60406000205416613e865750600090565b6040516370a0823160e01b815230600482015290602082602481845afa918215613dc257600092613ede575b506000526007602052604060002054808211600014613ed757613ed491613b6c565b90565b5050600090565b90916020823d602011613f0a575b81613ef9602093836139c3565b8101031261033b5750519038613eb2565b3d9150613eec565b60026000805160206147ce8339815191525414613f3d5760026000805160206147ce83398151915255565b633ee5aeb560e01b60005260046000fd5b60ff6000805160206147ae8339815191525416613f6757565b63d93c066560e01b60005260046000fd5b6001600160a01b0316600081815260136020526040902054801561404757816000526015602052604060002043600052602052613fbb6040600020549384613cb0565b92831061403657808260005260176020524360406000205414614018575b8311613ffc57506000526015602052604060002043600052602052604060002055565b925063029359d560e41b60005260045260245260445260646000fd5b5081600052601960205261403160406000205482613cb0565b613fd9565b63446c8cc960e01b60005260046000fd5b505050565b614081614086939261407360405194859263a9059cbb60e01b602085015260248401613bc2565b03601f1981018452836139c3565b6142ed565b565b600081815260008051602061478e8339815191526020908152604080832033845290915290205460ff16156140ba5750565b63e2517d3f60e01b6000523360045260245260446000fd5b6140dc8282614348565b91826140e757505090565b60009182526000805160206146ae8339815191526020526040909120614116916001600160a01b0316906144d0565b5090565b61412482826143ea565b918261412f57505090565b60009182526000805160206146ae8339815191526020526040909120614116916001600160a01b031690614539565b6001600160a01b03166000818152601260205260409020548015614047578160005260146020526040600020436000526020526141a16040600020549384613cb0565b928310614036578082600052601660205243604060002054146141fe575b83116141e257506000526014602052604060002043600052602052604060002055565b92506319a4e2a760e01b60005260045260245260445260646000fd5b5081600052601860205261421760406000205482613cb0565b6141bf565b6040516323b872dd60e01b60208201526001600160a01b039283166024820152929091166044830152606480830193909352918152614086916140816084836139c3565b60405163313ce56760e01b81529190602090839060049082906001600160a01b03165afa600092816142cc575b506142a357631eecbb6560e01b60005260046000fd5b60ff91821691168082036142b5575050565b631e43476360e11b60005260045260245260446000fd5b6142e691935060203d6020116109c9576109ba81836139c3565b913861428d565b906000602091828151910182855af115613dc2576000513d61433f57506001600160a01b0381163b155b61431e5750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b60011415614317565b600081815260008051602061478e833981519152602090815260408083206001600160a01b038616845290915290205460ff16613ed757600081815260008051602061478e833981519152602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9080a4600190565b600081815260008051602061478e833981519152602090815260408083206001600160a01b038616845290915290205460ff1615613ed757600081815260008051602061478e833981519152602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4600190565b60ff60008051602061480e8339815191525460401c16156144a757565b631afcd79f60e31b60005260046000fd5b8054821015613c205760005260206000200190600090565b6001810190826000528160205260406000205415600014613dce578054600160401b8110156139e65761452461450d8260018794018555846144b8565b819391549060031b91821b91600019901b19161790565b90555491600052602052604060002055600190565b9060018201918160005282602052604060002054801515600014614603576000198101818111613a9e578254600019810191908211613a9e578181036145cc575b505050805480156145b657600019019061459482826144b8565b8154906000199060031b1b191690555560005260205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b6145ec6145dc61450d93866144b8565b90549060031b1c928392866144b8565b90556000528360205260406000205538808061457a565b50505050600090565b90614632575080511561462157602081519101fd5b63d6bda27560e01b60005260046000fd5b81511580614664575b614643575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b1561463b56fe2c0a5f5ab0ba371221bb3612c2d2f90d36566279309f526000c5b354efd1b0658c67aaac7f77de07240e5a9b36dfdccb2a89c438635abbb27a73142d2aa0f629c1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e8237170593200048b07fab687f450848f1df8a96db08fef7840f53415f18fdc2ec8e9f810f0930c7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d275027301df982f5215b4963a854cf9c73219df80cb6acd8f098189c9480c7bbc360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc17dc5b1290f7495d7f8cc68476e6941aaf510598fa925d0d2720edc8805a846c4460507c21097c15f6883572ebd157a9ee4b1d989a312314e93afd9d2ecd494002dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800cd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033009b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006d129bd71f2401cbe77f49f546d7d5f58992faecfca6e51ba06b50b261e83331a164736f6c6343000822000a";
14507
15610
  var isSuperArgs = (xs) => xs.length > 1;
14508
- var SimplePSMV2__factory = class extends import_ethers12.ContractFactory {
15611
+ var SimplePSMV2__factory = class extends import_ethers13.ContractFactory {
14509
15612
  constructor(...args) {
14510
15613
  if (isSuperArgs(args)) {
14511
15614
  super(...args);
@@ -14525,10 +15628,10 @@ var SimplePSMV2__factory = class extends import_ethers12.ContractFactory {
14525
15628
  static bytecode = _bytecode;
14526
15629
  static abi = _abi;
14527
15630
  static createInterface() {
14528
- return new import_ethers12.Interface(_abi);
15631
+ return new import_ethers13.Interface(_abi);
14529
15632
  }
14530
15633
  static connect(address, runner) {
14531
- return new import_ethers12.Contract(address, _abi, runner);
15634
+ return new import_ethers13.Contract(address, _abi, runner);
14532
15635
  }
14533
15636
  };
14534
15637
 
@@ -15000,6 +16103,15 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15000
16103
  return {};
15001
16104
  return this.serverSession.getAuthHeader();
15002
16105
  }
16106
+ /**
16107
+ * Remove the persisted lit-ops-server session (the 24h re-mint envelope and
16108
+ * its JWT) for the current auth signer. Call on wallet disconnect or
16109
+ * explicit sign-out so the credential does not outlive the wallet
16110
+ * connection. No-op in standalone mode.
16111
+ */
16112
+ async clearServerSession() {
16113
+ await this.serverSession?.clearPersisted();
16114
+ }
15003
16115
  /**
15004
16116
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
15005
16117
  * the post-write state. We clear the entire loan-query cache (not just
@@ -15095,7 +16207,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15095
16207
  */
15096
16208
  constructor(config) {
15097
16209
  if (!config.provider && config.ethRpcUrl) {
15098
- config.provider = new import_ethers13.JsonRpcProvider(config.ethRpcUrl);
16210
+ config.provider = new import_ethers14.JsonRpcProvider(config.ethRpcUrl);
15099
16211
  }
15100
16212
  this.config = config;
15101
16213
  if (config.debug) {
@@ -15111,7 +16223,10 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15111
16223
  this.serverSession = new ServerSession({
15112
16224
  signer: config.authSigner ?? config.contractSigner,
15113
16225
  serviceEndpoint: config.serviceEndpoint,
15114
- chainId: config.chainId
16226
+ chainId: config.chainId,
16227
+ persistSession: config.sessionPersistence?.enabled,
16228
+ sessionStore: config.sessionPersistence?.store,
16229
+ onSignaturePrompt: config.onSessionSignaturePrompt
15115
16230
  });
15116
16231
  }
15117
16232
  const contractManagerResult = createContractManager({
@@ -15361,7 +16476,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15361
16476
  const network = await config.provider.getNetwork();
15362
16477
  chainId = Number(network.chainId);
15363
16478
  } else if (config.ethRpcUrl) {
15364
- const tempProvider = new import_ethers13.JsonRpcProvider(
16479
+ const tempProvider = new import_ethers14.JsonRpcProvider(
15365
16480
  config.ethRpcUrl
15366
16481
  );
15367
16482
  const network = await tempProvider.getNetwork();
@@ -15884,7 +16999,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15884
16999
  }
15885
17000
  const loanOps = this.loanOps();
15886
17001
  const protocolConfig = await loanOps.getProtocolConfig();
15887
- const requestAmountWei = (0, import_ethers13.parseEther)(
17002
+ const requestAmountWei = (0, import_ethers14.parseEther)(
15888
17003
  request.amount.toString()
15889
17004
  );
15890
17005
  const minLoanValueWei = BigInt(protocolConfig.minimumLoanValueWei);
@@ -15966,7 +17081,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
15966
17081
  pkpNftAddress,
15967
17082
  this.getChipotlePublicKeyFallback()
15968
17083
  );
15969
- pkpEthAddress = (0, import_ethers13.computeAddress)(pkpPublicKey);
17084
+ pkpEthAddress = (0, import_ethers14.computeAddress)(pkpPublicKey);
15970
17085
  pkpCache.set(request.positionId, {
15971
17086
  publicKey: pkpPublicKey,
15972
17087
  ethAddress: pkpEthAddress,
@@ -16295,8 +17410,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16295
17410
  throw new Error(`Position not found: ${request.positionId}`);
16296
17411
  }
16297
17412
  const currentDebt = position.ucdDebt.toString();
16298
- const ucdDebtHash = (0, import_ethers13.keccak256)(import_ethers13.AbiCoder.defaultAbiCoder().encode(["uint256"], [currentDebt]));
16299
- const contractHash = (0, import_ethers13.keccak256)(import_ethers13.AbiCoder.defaultAbiCoder().encode(
17413
+ const ucdDebtHash = (0, import_ethers14.keccak256)(import_ethers14.AbiCoder.defaultAbiCoder().encode(["uint256"], [currentDebt]));
17414
+ const contractHash = (0, import_ethers14.keccak256)(import_ethers14.AbiCoder.defaultAbiCoder().encode(
16300
17415
  ["address", "address", "address", "address"],
16301
17416
  [
16302
17417
  positionManagerAddress,
@@ -16308,262 +17423,54 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16308
17423
  if (this.config.debug) {
16309
17424
  log.info("\u{1F50D} DEBUG: Detailed Contract Call Parameters", {});
16310
17425
  log.info(" === LIT Action Response ===", {});
16311
- log.info(
16312
- ` authorizedSpendsHash: ${validationResponse.authorizedSpendsHash}`,
16313
- {}
16314
- );
16315
- log.info(` mintAmount: ${validationResponse.mintAmount}`, {});
16316
- log.info(` mintFee: ${validationResponse.mintFee}`, {});
16317
- log.info(` newDebt: ${validationResponse.newDebt}`, {});
16318
- log.info(` newCollateral: ${validationResponse.newCollateral}`, {});
16319
- log.info(` btcPrice: ${validationResponse.btcPrice}`, {});
16320
- log.info(
16321
- ` timestamp (quantumTimestamp): ${validationResponse.timestamp}`,
16322
- {}
16323
- );
16324
- log.info(" === SDK Calculated Hashes ===", {});
16325
- log.info(` Current UCD Debt (from position): ${currentDebt}`, {});
16326
- log.info(` ucdDebtHash: ${ucdDebtHash}`, {});
16327
- log.info(" === Contract Addresses for contractHash ===", {});
16328
- log.info(` PositionManager: ${positionManagerAddress}`, {});
16329
- log.info(
16330
- ` LoanOpsManager: ${this.getContractAddressesOrThrow().loanOperationsManager}`,
16331
- {}
16332
- );
16333
- log.info(
16334
- ` TermManager: ${this.getContractAddressesOrThrow().termManager}`,
16335
- {}
16336
- );
16337
- log.info(
16338
- ` UCDController: ${this.getContractAddressesOrThrow().ucdController}`,
16339
- {}
16340
- );
16341
- log.info(` contractHash: ${contractHash}`, {});
16342
- log.info(" === Signature ===", {});
16343
- log.info(
16344
- ` Signature (first 20 chars): ${finalSignature.substring(
16345
- 0,
16346
- 20
16347
- )}...`,
16348
- {}
16349
- );
16350
- log.info(` Signature length: ${finalSignature.length}`, {});
16351
- log.info(
16352
- ` Final signature (first 50 chars): ${finalSignature ? finalSignature.substring(0, 50) : "EMPTY"}`,
16353
- {}
16354
- );
16355
- log.info(
16356
- ` Final signature is empty: ${!finalSignature || finalSignature.length === 0}`,
16357
- {}
16358
- );
16359
- }
16360
- if (this.config.debug) {
16361
- log.info("\u{1F50D} PREFLIGHT: Attempting diagnostic gas estimation...", {});
16362
- }
16363
- try {
16364
- const iface = new import_ethers13.Interface(positionManagerAbi);
16365
- const signatureHex = finalSignature.startsWith("0x") ? finalSignature : "0x" + finalSignature;
16366
- if (this.config.debug) {
16367
- log.info("\u{1F50D} DEBUG: Signature details:", {
16368
- finalSignature: finalSignature ? `${finalSignature.substring(0, 20)}... (length: ${finalSignature.length})` : "UNDEFINED/EMPTY",
16369
- signatureHex: signatureHex ? `${signatureHex.substring(0, 22)}... (length: ${signatureHex.length})` : "UNDEFINED/EMPTY"
16370
- });
16371
- }
16372
- const calldata = iface.encodeFunctionData("mintUCD", [
16373
- positionIdBytes32,
16374
- validationResponse.mintAmount,
16375
- validationResponse.mintFee,
16376
- validationResponse.newDebt,
16377
- validationResponse.newCollateral,
16378
- validationResponse.btcPrice,
16379
- validationResponse.authorizedSpendsHash,
16380
- ucdDebtHash,
16381
- contractHash,
16382
- validationResponse.timestamp,
16383
- signatureHex
16384
- ]);
16385
- if (this.config.debug) {
16386
- log.info("\u{1F50D} DEBUG: Encoded calldata:", {
16387
- length: calldata.length,
16388
- signaturePortion: calldata.substring(650, 670)
16389
- });
16390
- }
16391
- const provider = this.getSignerOrThrow().provider;
16392
- if (!provider) {
16393
- throw new Error("Provider not available");
16394
- }
16395
- const signerAddress = await this.getSignerOrThrow().getAddress();
16396
- if (this.config.debug) {
16397
- log.info(" Executing diagnostic eth_call...", {});
16398
- }
16399
- await provider.call({
16400
- to: positionManagerAddress,
16401
- from: signerAddress,
16402
- data: calldata
16403
- });
16404
- if (this.config.debug) {
16405
- log.info(" \u2705 Diagnostic eth_call succeeded!", {});
16406
- }
16407
- } catch (preflightError) {
16408
- if (this.config.debug) {
16409
- log.error(
16410
- "\u274C PREFLIGHT CHECK FAILED - Diagnostic eth_call reverted",
16411
- {}
16412
- );
16413
- log.error(` Error message: ${preflightError?.message}`, {});
16414
- log.error(` Error code: ${preflightError?.code}`, {});
16415
- log.error(` Error data: ${preflightError?.data || "none"}`, {});
16416
- log.error(` Error reason: ${preflightError?.reason || "none"}`, {});
16417
- log.error(
16418
- ` Error.error: ${JSON.stringify(preflightError?.error || {})}`,
16419
- {}
16420
- );
16421
- }
16422
- let errorData = preflightError?.data || preflightError?.info?.error?.data || preflightError?.error?.data?.data || preflightError?.error?.data || preflightError?.error?.error?.data;
16423
- const errorMessage = preflightError?.reason || preflightError?.error?.reason || preflightError?.error?.error?.reason || preflightError?.message || "Unknown error";
16424
- let requireMessage;
16425
- if (errorData && typeof errorData === "string" && errorData.startsWith("0x") && errorData.length > 138) {
16426
- try {
16427
- const errorSelector = errorData.slice(0, 10);
16428
- if (errorSelector === "0x08c379a0") {
16429
- const iface = new import_ethers13.Interface([
16430
- "function Error(string)"
16431
- ]);
16432
- const decoded = iface.decodeFunctionData("Error", errorData);
16433
- requireMessage = decoded[0];
16434
- }
16435
- } catch (e) {
16436
- }
16437
- }
16438
- const isDeadZonePreflightArtifact = decodeQuantumRevert(preflightError).selector === DEAD_ZONE_VIOLATION_SELECTOR;
16439
- if (isDeadZonePreflightArtifact) {
16440
- const nowSec = Math.floor(Date.now() / 1e3);
16441
- const sigQuantum2 = Math.floor(Number(validationResponse.timestamp) / 60) * 60;
16442
- const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
16443
- if (sigQuantum2 === realCurrentQuantum) {
16444
- if (this.config.debug) {
16445
- log.info(
16446
- "\u21AA\uFE0F Preflight DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
16447
- {}
16448
- );
16449
- }
16450
- } else {
16451
- if (this.config.debug) {
16452
- log.info(
16453
- "\u{1F501} Preflight DeadZoneViolation near the boundary \u2014 deferring across it, then proceeding",
16454
- {}
16455
- );
16456
- }
16457
- await awaitSafeSubmissionWindow(
16458
- Number(validationResponse.timestamp)
16459
- );
16460
- }
16461
- }
16462
- if (requireMessage && !isDeadZonePreflightArtifact) {
16463
- throw new Error(
16464
- `Preflight check failed - mintUCD would revert:
16465
- ${requireMessage}
16466
- Context: Quantum timestamp=${validationResponse.timestamp}, Position=${request.positionId}`
16467
- );
16468
- }
16469
- if (errorData && typeof errorData === "string" && errorData.startsWith("0x") && errorData.length > 10) {
16470
- const selector = errorData.slice(0, 10);
16471
- const knownErrors = {
16472
- "0x8baa579f": "InvalidSignature()",
16473
- "0xd92e233d": "ValidationFailed()",
16474
- "0x3ee5aeb5": "OperationNotAuthorized()",
16475
- "0x70f4b32a": "UnauthorizedSigner()",
16476
- "0x48f5c3ed": "Unauthorized()",
16477
- "0x36bfdeea": "StateHashMismatch()",
16478
- "0x131d9a21": "QuantumExpired()",
16479
- "0x52ce5d58": "QuantumAlreadyUsed()",
16480
- "0x137f3b70": "InDeadZone()",
16481
- "0xbe4b82c1": "DeadZoneViolation()",
16482
- "0x3e76a3c9": "QuantumOutsideWindow()",
16483
- "0x62278171": "InvalidValidatorSignature()",
16484
- "0xb9d419a7": "DebtUpdateVerificationFailed()",
16485
- "0xc7e20553": "DebtUpdateVerificationFailedDetailed(bytes32,uint256,uint256,uint256,uint256,uint256)",
16486
- "0x0cfd2a97": "MintVerificationMismatch(uint256,uint256)",
16487
- "0x07637bd8": "MintFailed()"
16488
- };
16489
- const errorName = knownErrors[selector] || `Unknown error ${selector}`;
16490
- const mintDebtDiagnostics = selector === "0xb9d419a7" || selector === "0xc7e20553" || selector === "0x0cfd2a97" || selector === "0x07637bd8" ? `
16491
-
16492
- Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMint):
16493
- positionId: ${request.positionId}
16494
- on-chain ucdDebt before tx: ${currentDebt}
16495
- Lit newDebt: ${validationResponse.newDebt}
16496
- Lit mintAmount: ${validationResponse.mintAmount}
16497
- Lit mintFee: ${validationResponse.mintFee}
16498
- Call path: mintUCD -> mintUCDWithAuthorization -> increaseDebtFromMint -> finalizeMintDebtIncrease
16499
- Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
16500
- ` : "";
16501
- let quantumContext = "";
16502
- if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0xbe4b82c1" || selector === "0x3e76a3c9") {
16503
- const currentTime2 = Math.floor(Date.now() / 1e3);
16504
- const currentQuantum = Math.floor(currentTime2 / 60) * 60;
16505
- const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
16506
- const timeDiff = currentTime2 - validationResponse.timestamp;
16507
- quantumContext = `
16508
-
16509
- Quantum Timing Analysis:
16510
- Signature created: ${validationResponse.timestamp} (${new Date(
16511
- validationResponse.timestamp * 1e3
16512
- ).toISOString()})
16513
- Current time: ${currentTime2} (${new Date(
16514
- currentTime2 * 1e3
16515
- ).toISOString()})
16516
- Time elapsed: ${timeDiff} seconds
16517
- Signature quantum: ${sigQuantum2}
16518
- Current quantum: ${currentQuantum}
16519
- Quantum window: 60 seconds
16520
- Status: ${sigQuantum2 < currentQuantum ? "\u274C EXPIRED - Signature created in older quantum window" : "\u2705 VALID"}
16521
- Note: QuantumOutsideWindow can also occur if the tx is mined outside the accepted past/current/next quantum set.`;
16522
- }
16523
- if (!isDeadZonePreflightArtifact) {
16524
- throw new Error(
16525
- `Preflight check failed - mintUCD would revert:
16526
- Error: ${errorName}
16527
- Selector: ${selector}
16528
- Message: ${errorMessage}
16529
- Data: ${errorData}${quantumContext}${mintDebtDiagnostics}`
16530
- );
16531
- }
16532
- } else {
16533
- const currentTime2 = Math.floor(Date.now() / 1e3);
16534
- const timeSinceSig = currentTime2 - validationResponse.timestamp;
16535
- const currentQuantum = Math.floor(currentTime2 / 60) * 60;
16536
- const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
16537
- throw new Error(
16538
- `Preflight check failed - mintUCD would revert:
16539
- ${errorMessage}
16540
-
16541
- Error Details:
16542
- Error data: ${errorData || "0x (empty - no custom error)"}
16543
- Error code: ${preflightError?.code || "none"}
16544
-
16545
- Quantum Timing Analysis:
16546
- Signature created: ${validationResponse.timestamp} (${new Date(
16547
- validationResponse.timestamp * 1e3
16548
- ).toISOString()})
16549
- Current time: ${currentTime2} (${new Date(
16550
- currentTime2 * 1e3
16551
- ).toISOString()})
16552
- Time elapsed: ${timeSinceSig} seconds
16553
- Signature quantum: ${sigQuantum2}
16554
- Current quantum: ${currentQuantum}
16555
- Status: ${sigQuantum2 < currentQuantum ? "\u274C EXPIRED - Signature too old" : "\u2705 VALID"}
16556
-
16557
- Possible Causes (when error data is empty):
16558
- \u2022 Quantum signature expired (current=${currentQuantum}, signature=${sigQuantum2})
16559
- \u2022 Contract paused or circuit breaker active
16560
- \u2022 Position state changed since LIT Action signed
16561
- \u2022 Authorization registry validation failed
16562
- \u2022 RPC provider not returning detailed revert data
16563
-
16564
- Position: ${request.positionId}`
16565
- );
16566
- }
17426
+ log.info(
17427
+ ` authorizedSpendsHash: ${validationResponse.authorizedSpendsHash}`,
17428
+ {}
17429
+ );
17430
+ log.info(` mintAmount: ${validationResponse.mintAmount}`, {});
17431
+ log.info(` mintFee: ${validationResponse.mintFee}`, {});
17432
+ log.info(` newDebt: ${validationResponse.newDebt}`, {});
17433
+ log.info(` newCollateral: ${validationResponse.newCollateral}`, {});
17434
+ log.info(` btcPrice: ${validationResponse.btcPrice}`, {});
17435
+ log.info(
17436
+ ` timestamp (quantumTimestamp): ${validationResponse.timestamp}`,
17437
+ {}
17438
+ );
17439
+ log.info(" === SDK Calculated Hashes ===", {});
17440
+ log.info(` Current UCD Debt (from position): ${currentDebt}`, {});
17441
+ log.info(` ucdDebtHash: ${ucdDebtHash}`, {});
17442
+ log.info(" === Contract Addresses for contractHash ===", {});
17443
+ log.info(` PositionManager: ${positionManagerAddress}`, {});
17444
+ log.info(
17445
+ ` LoanOpsManager: ${this.getContractAddressesOrThrow().loanOperationsManager}`,
17446
+ {}
17447
+ );
17448
+ log.info(
17449
+ ` TermManager: ${this.getContractAddressesOrThrow().termManager}`,
17450
+ {}
17451
+ );
17452
+ log.info(
17453
+ ` UCDController: ${this.getContractAddressesOrThrow().ucdController}`,
17454
+ {}
17455
+ );
17456
+ log.info(` contractHash: ${contractHash}`, {});
17457
+ log.info(" === Signature ===", {});
17458
+ log.info(
17459
+ ` Signature (first 20 chars): ${finalSignature.substring(
17460
+ 0,
17461
+ 20
17462
+ )}...`,
17463
+ {}
17464
+ );
17465
+ log.info(` Signature length: ${finalSignature.length}`, {});
17466
+ log.info(
17467
+ ` Final signature (first 50 chars): ${finalSignature ? finalSignature.substring(0, 50) : "EMPTY"}`,
17468
+ {}
17469
+ );
17470
+ log.info(
17471
+ ` Final signature is empty: ${!finalSignature || finalSignature.length === 0}`,
17472
+ {}
17473
+ );
16567
17474
  }
16568
17475
  try {
16569
17476
  const coreAddress = this.getContractAddressesOrThrow().positionManagerCore;
@@ -16573,7 +17480,7 @@ Position: ${request.positionId}`
16573
17480
  const coreAbi = [
16574
17481
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
16575
17482
  ];
16576
- const positionCore = new import_ethers13.Contract(
17483
+ const positionCore = new import_ethers14.Contract(
16577
17484
  coreAddress,
16578
17485
  coreAbi,
16579
17486
  this.getSignerOrThrow().provider
@@ -16581,8 +17488,8 @@ Position: ${request.positionId}`
16581
17488
  const currentPosition = await positionCore["getPositionDetails"](
16582
17489
  positionIdBytes32
16583
17490
  );
16584
- const currentDebtHash = (0, import_ethers13.keccak256)(
16585
- import_ethers13.AbiCoder.defaultAbiCoder().encode(
17491
+ const currentDebtHash = (0, import_ethers14.keccak256)(
17492
+ import_ethers14.AbiCoder.defaultAbiCoder().encode(
16586
17493
  ["uint256"],
16587
17494
  [currentPosition.ucdDebt]
16588
17495
  )
@@ -16629,7 +17536,7 @@ Position: ${request.positionId}`
16629
17536
  "function getCurrentQuantum() view returns (uint256)",
16630
17537
  "function isQuantumValid(bytes32,uint256) view returns (bool)"
16631
17538
  ];
16632
- const registry = new import_ethers13.Contract(
17539
+ const registry = new import_ethers14.Contract(
16633
17540
  registryAddress,
16634
17541
  registryAbi,
16635
17542
  this.getSignerOrThrow().provider
@@ -16745,7 +17652,7 @@ Position: ${request.positionId}`
16745
17652
  });
16746
17653
  }
16747
17654
  const signatureHexMint = finalSignature.startsWith("0x") ? finalSignature : "0x" + finalSignature;
16748
- const mintIface = new import_ethers13.Interface(positionManagerAbi);
17655
+ const mintIface = new import_ethers14.Interface(positionManagerAbi);
16749
17656
  const mintCalldata = mintIface.encodeFunctionData("mintUCD", [
16750
17657
  positionIdBytes32,
16751
17658
  validationResponse.mintAmount,
@@ -16760,146 +17667,121 @@ Position: ${request.positionId}`
16760
17667
  signatureHexMint
16761
17668
  ]);
16762
17669
  const fromAddress = await signer.getAddress();
16763
- const estimatedGas = await estimateContractCallGasWithMargin(
16764
- signerProvider,
16765
- fromAddress,
16766
- positionManagerAddress,
16767
- mintCalldata,
16768
- MINT_UCD_ESTIMATE_MARGIN_BPS,
16769
- MINT_UCD_GAS_CEILING
16770
- );
16771
- let tx;
16772
- if (estimatedGas !== null) {
16773
- if (this.config.debug) {
16774
- log.info(
16775
- `\u{1F4DD} Broadcasting mintUCD (gasLimit=${estimatedGas.toString()})`,
16776
- {}
16777
- );
16778
- }
16779
- await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
16780
- tx = await sendEip1559Transaction({
16781
- signer,
17670
+ try {
17671
+ await assertSafeQuantumSubmission({
17672
+ provider: signerProvider,
16782
17673
  to: positionManagerAddress,
17674
+ from: fromAddress,
16783
17675
  data: mintCalldata,
16784
- gasLimit: estimatedGas
17676
+ quantumTimestamp: Number(validationResponse.timestamp),
17677
+ onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
16785
17678
  });
16786
- } else {
16787
- if (this.config.debug) {
16788
- log.info(
16789
- "\u{1F50D} eth_estimateGas failed for mintUCD; running static call",
16790
- {}
16791
- );
16792
- }
16793
- try {
16794
- await signerProvider.call({
16795
- to: positionManagerAddress,
16796
- from: fromAddress,
16797
- data: mintCalldata
16798
- });
16799
- } catch (callError) {
16800
- if (this.config.debug) {
16801
- log.info("\u{1F50D} DEBUG: mintUCD static call failed", {});
16802
- log.info(` Error message: ${callError?.message}`, {});
16803
- log.info(` Error code: ${callError?.code}`, {});
16804
- log.info(` Error data type: ${typeof callError?.data}`, {});
16805
- log.info(` Error data value: ${callError?.data}`, {});
16806
- }
16807
- let errorData = callError?.data || callError?.info?.error?.data || callError?.error?.data?.data || callError?.error?.data;
16808
- const errorMessage = callError?.reason || callError?.message || "Unknown error";
16809
- const isDeadZoneArtifact = decodeQuantumRevert(callError).selector === DEAD_ZONE_VIOLATION_SELECTOR;
16810
- if (isDeadZoneArtifact && this.config.debug) {
16811
- log.info(
16812
- "\u{1F536} mintUCD static-call DeadZoneViolation is a stale-latest-block artifact; broadcasting anyway (gated)",
16813
- {}
16814
- );
16815
- }
16816
- if (!isDeadZoneArtifact && errorData && typeof errorData === "string" && errorData.startsWith("0x") && errorData.length > 10) {
16817
- const selector = errorData.slice(0, 10);
16818
- const knownErrors = {
16819
- "0x8baa579f": "InvalidSignature()",
16820
- "0xd92e233d": "ValidationFailed()",
16821
- "0x3ee5aeb5": "OperationNotAuthorized()",
16822
- "0x70f4b32a": "UnauthorizedSigner()",
16823
- "0x48f5c3ed": "Unauthorized()",
16824
- "0x36bfdeea": "StateHashMismatch()",
16825
- "0x131d9a21": "QuantumExpired()",
16826
- "0x52ce5d58": "QuantumAlreadyUsed()",
16827
- "0x137f3b70": "InDeadZone()",
16828
- "0xbe4b82c1": "DeadZoneViolation()",
16829
- "0x3e76a3c9": "QuantumOutsideWindow()",
16830
- "0x62278171": "InvalidValidatorSignature()",
16831
- "0xb9d419a7": "DebtUpdateVerificationFailed()",
16832
- "0xc7e20553": "DebtUpdateVerificationFailedDetailed(bytes32,uint256,uint256,uint256,uint256,uint256)",
16833
- "0x0cfd2a97": "MintVerificationMismatch(uint256,uint256)",
16834
- "0x07637bd8": "MintFailed()"
16835
- };
16836
- const errorName = knownErrors[selector] || `Unknown error ${selector}`;
16837
- throw new Error(
16838
- `mintUCD reverted: ${errorName}
16839
- Error selector: ${selector}
16840
- Error data: ${errorData}
16841
- Call path: mintUCD -> mintUCDWithAuthorization -> increaseDebtFromMint -> finalizeMintDebtIncrease
16842
- Message: ${errorMessage}`
16843
- );
16844
- }
16845
- if (errorData === "0x" || !errorData) {
16846
- const viewsAddress = this.config.contractAddresses?.positionManagerViews;
16847
- if (viewsAddress) {
16848
- try {
16849
- const views = new import_ethers13.Contract(
16850
- viewsAddress,
16851
- [
16852
- "function diagnoseMintUCD(bytes32,uint256,uint256,uint256,uint256,uint256,bytes32,bytes32,bytes32,uint256,bytes) view returns (string)"
16853
- ],
16854
- signerProvider
16855
- );
16856
- const diagnosis = await views["diagnoseMintUCD"](
16857
- request.positionId,
16858
- validationResponse.mintAmount,
16859
- validationResponse.mintFee,
16860
- validationResponse.newDebt,
16861
- validationResponse.newCollateral,
16862
- validationResponse.btcPrice,
16863
- validationResponse.authorizedSpendsHash,
16864
- ucdDebtHash,
16865
- contractHash,
16866
- validationResponse.timestamp,
16867
- signatureHexMint
16868
- );
16869
- throw new Error(
16870
- `mintUCD reverted with empty data; diagnosis: ${diagnosis}`
17679
+ } catch (simError) {
17680
+ if (!(simError instanceof QuantumRevertError)) {
17681
+ throw simError;
17682
+ }
17683
+ const simCause = simError.cause;
17684
+ const causeMessage = simCause?.reason ?? simCause?.shortMessage ?? (simCause instanceof Error ? simCause.message : String(simCause));
17685
+ if (!simError.data) {
17686
+ const viewsAddress = this.config.contractAddresses?.positionManagerViews;
17687
+ if (viewsAddress) {
17688
+ let diagnosis = null;
17689
+ try {
17690
+ const views = new import_ethers14.Contract(
17691
+ viewsAddress,
17692
+ [
17693
+ "function diagnoseMintUCD(bytes32,uint256,uint256,uint256,uint256,uint256,bytes32,bytes32,bytes32,uint256,bytes) view returns (string)"
17694
+ ],
17695
+ signerProvider
17696
+ );
17697
+ diagnosis = await views["diagnoseMintUCD"](
17698
+ request.positionId,
17699
+ validationResponse.mintAmount,
17700
+ validationResponse.mintFee,
17701
+ validationResponse.newDebt,
17702
+ validationResponse.newCollateral,
17703
+ validationResponse.btcPrice,
17704
+ validationResponse.authorizedSpendsHash,
17705
+ ucdDebtHash,
17706
+ contractHash,
17707
+ validationResponse.timestamp,
17708
+ signatureHexMint
17709
+ );
17710
+ } catch (diagErr) {
17711
+ if (this.config.debug) {
17712
+ log.info(
17713
+ ` diagnoseMintUCD unavailable or failed: ${diagErr?.message || diagErr}`,
17714
+ {}
16871
17715
  );
16872
- } catch (diagErr) {
16873
- if (this.config.debug) {
16874
- log.info(
16875
- ` diagnoseMintUCD unavailable or failed: ${diagErr?.message || diagErr}`,
16876
- {}
16877
- );
16878
- }
16879
17716
  }
16880
17717
  }
17718
+ if (diagnosis !== null) {
17719
+ throw new Error(
17720
+ `mintUCD reverted with empty data; diagnosis: ${diagnosis}`
17721
+ );
17722
+ }
16881
17723
  }
16882
- if (!isDeadZoneArtifact) {
16883
- throw new Error(
16884
- `mintUCD reverted: ${errorMessage}
16885
- Error data: ${errorData || "none"}`
16886
- );
16887
- }
16888
- }
16889
- if (this.config.debug) {
16890
- log.info(
16891
- "\u2705 Static call clear \u2014 broadcasting mintUCD with fixed gas ceiling",
16892
- {}
17724
+ throw new Error(
17725
+ `mintUCD would revert (pre-send simulation): ${causeMessage}
17726
+ Error data: none`
16893
17727
  );
16894
17728
  }
16895
- await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
16896
- tx = await sendEip1559Transaction({
16897
- signer,
16898
- to: positionManagerAddress,
16899
- data: mintCalldata,
16900
- gasLimit: MINT_UCD_GAS_CEILING
16901
- });
17729
+ const selector = simError.selector;
17730
+ const errorName = simError.errorName ?? (selector ? `Unknown error ${selector}` : "unknown");
17731
+ const mintDebtDiagnostics = selector === "0xb9d419a7" || // DebtUpdateVerificationFailed()
17732
+ selector === "0xc7e20553" || // DebtUpdateVerificationFailedDetailed(...)
17733
+ selector === "0x0cfd2a97" || // MintVerificationMismatch(uint256,uint256)
17734
+ selector === "0x07637bd8" ? `
17735
+
17736
+ Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMint):
17737
+ positionId: ${request.positionId}
17738
+ on-chain ucdDebt before tx: ${currentDebt}
17739
+ Lit newDebt: ${validationResponse.newDebt}
17740
+ Lit mintAmount: ${validationResponse.mintAmount}
17741
+ Lit mintFee: ${validationResponse.mintFee}
17742
+ Call path: mintUCD -> mintUCDWithAuthorization -> increaseDebtFromMint -> finalizeMintDebtIncrease
17743
+ Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
17744
+ ` : "";
17745
+ let quantumContext = "";
17746
+ if (selector === "0x131d9a21" || // QuantumExpired()
17747
+ selector === "0x52ce5d58" || // QuantumAlreadyUsed()
17748
+ selector === "0x137f3b70" || // InDeadZone()
17749
+ selector === "0xbe4b82c1" || // DeadZoneViolation()
17750
+ selector === "0x3e76a3c9") {
17751
+ const nowSecs = Math.floor(Date.now() / 1e3);
17752
+ const nowQuantum = Math.floor(nowSecs / 60) * 60;
17753
+ const signedQuantum = Math.floor(Number(validationResponse.timestamp) / 60) * 60;
17754
+ const timeDiff = nowSecs - Number(validationResponse.timestamp);
17755
+ quantumContext = `
17756
+
17757
+ Quantum Timing Analysis:
17758
+ Signature created: ${validationResponse.timestamp} (${new Date(
17759
+ Number(validationResponse.timestamp) * 1e3
17760
+ ).toISOString()})
17761
+ Current time: ${nowSecs} (${new Date(
17762
+ nowSecs * 1e3
17763
+ ).toISOString()})
17764
+ Time elapsed: ${timeDiff} seconds
17765
+ Signature quantum: ${signedQuantum}
17766
+ Current quantum: ${nowQuantum}
17767
+ Quantum window: 60 seconds
17768
+ Status: ${signedQuantum < nowQuantum ? "\u274C EXPIRED - Signature created in older quantum window" : "\u2705 VALID"}
17769
+ Note: QuantumOutsideWindow can also occur if the tx is mined outside the accepted past/current/next quantum set.`;
17770
+ }
17771
+ throw new Error(
17772
+ `mintUCD would revert (pre-send simulation): ${errorName}
17773
+ Error selector: ${selector}
17774
+ Error data: ${simError.data}
17775
+ Call path: mintUCD -> mintUCDWithAuthorization -> increaseDebtFromMint -> finalizeMintDebtIncrease
17776
+ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
17777
+ );
16902
17778
  }
17779
+ const tx = await sendEip1559Transaction({
17780
+ signer,
17781
+ to: positionManagerAddress,
17782
+ data: mintCalldata,
17783
+ gasLimit: MINT_UCD_GAS_CEILING
17784
+ });
16903
17785
  const receipt = await signerProvider.waitForTransaction(
16904
17786
  tx.hash,
16905
17787
  1,
@@ -16915,7 +17797,7 @@ Error data: ${errorData || "none"}`
16915
17797
  if (receipt.logs && receipt.logs.length > 0) {
16916
17798
  for (const receiptLog of receipt.logs) {
16917
17799
  if (receiptLog.topics && receiptLog.topics[0] === "0x08c379a0") {
16918
- const iface = new import_ethers13.Interface([
17800
+ const iface = new import_ethers14.Interface([
16919
17801
  "error Error(string)"
16920
17802
  ]);
16921
17803
  const decoded = iface.decodeErrorResult("Error", receiptLog.data);
@@ -16932,7 +17814,7 @@ Error data: ${errorData || "none"}`
16932
17814
  const minimalAbi = [
16933
17815
  "event UCDMintedWithAuthorization(bytes32 indexed positionId, address indexed borrower, uint256 mintAmount, uint256 mintFee, uint256 newDebt, uint256 newCollateral, uint256 btcPrice, uint256 quantumTimestamp, bytes32 authorizedSpendsHash)"
16934
17816
  ];
16935
- const iface = new import_ethers13.Interface(minimalAbi);
17817
+ const iface = new import_ethers14.Interface(minimalAbi);
16936
17818
  for (const receiptLog of receipt.logs) {
16937
17819
  const parsed = (() => {
16938
17820
  const logIface = iface;
@@ -17119,7 +18001,7 @@ Error data: ${errorData || "none"}`
17119
18001
  pkpNftAddress,
17120
18002
  this.getChipotlePublicKeyFallback()
17121
18003
  );
17122
- const pkpEthAddress = (0, import_ethers13.computeAddress)(pkpPublicKey);
18004
+ const pkpEthAddress = (0, import_ethers14.computeAddress)(pkpPublicKey);
17123
18005
  pkpData = {
17124
18006
  publicKey: pkpPublicKey,
17125
18007
  ethAddress: pkpEthAddress,
@@ -17521,7 +18403,7 @@ Error data: ${errorData || "none"}`
17521
18403
  }
17522
18404
  const loanOpsForUcd = loanOpsForUcdResult.value;
17523
18405
  const ucdTokenAddress = await loanOpsForUcd.ucdToken();
17524
- const ucdToken = new import_ethers13.Contract(
18406
+ const ucdToken = new import_ethers14.Contract(
17525
18407
  ucdTokenAddress,
17526
18408
  [
17527
18409
  "function balanceOf(address) view returns (uint256)",
@@ -17537,9 +18419,9 @@ Error data: ${errorData || "none"}`
17537
18419
  if (ucdBalance < debtWei) {
17538
18420
  return {
17539
18421
  success: false,
17540
- error: `Insufficient UCD balance for liquidation: ${(0, import_ethers13.formatEther)(
18422
+ error: `Insufficient UCD balance for liquidation: ${(0, import_ethers14.formatEther)(
17541
18423
  ucdBalance
17542
- )} UCD (need ${(0, import_ethers13.formatEther)(debtWei)} UCD)`,
18424
+ )} UCD (need ${(0, import_ethers14.formatEther)(debtWei)} UCD)`,
17543
18425
  positionId: request.positionId,
17544
18426
  wasLiquidated: false
17545
18427
  };
@@ -17548,7 +18430,7 @@ Error data: ${errorData || "none"}`
17548
18430
  if (request.skipUcdApproval) {
17549
18431
  return {
17550
18432
  success: false,
17551
- error: `Insufficient UCD allowance for UCDController: need at least ${(0, import_ethers13.formatEther)(
18433
+ error: `Insufficient UCD allowance for UCDController: need at least ${(0, import_ethers14.formatEther)(
17552
18434
  debtWei
17553
18435
  )} UCD, or omit skipUcdApproval to let the SDK submit approve`,
17554
18436
  positionId: request.positionId,
@@ -17985,9 +18867,12 @@ Error data: ${errorData || "none"}`
17985
18867
  userSignature: auth.signature,
17986
18868
  rpcUrl: _rpcUrl || this.config.ethRpcUrl,
17987
18869
  // Forward a Bitcoin provider so the validator reaches a reachable esplora on Hardhat
17988
- // (lit-ops maps it to the validator's devBitcoinProviderUrl); ignored off-hardhat. Prefer
18870
+ // Hardhat-ONLY: gives the validator a reachable esplora (lit-ops maps it to the
18871
+ // validator's devBitcoinProviderUrl). Never emitted on prod chains, where the provider
18872
+ // is resolved from the on-chain BitcoinProviderRegistry and this field is server-ignored
18873
+ // — emitting it there only misleads readers into thinking it is caller-controlled. Prefer
17989
18874
  // a per-request override (mirrors the mint path's customBitcoinRpcUrl) over the config default.
17990
- ...(_bitcoinRpcUrl || this.config.bitcoinProviders?.[0]?.url) && {
18875
+ ...this.config.chain === "hardhat" && (_bitcoinRpcUrl || this.config.bitcoinProviders?.[0]?.url) && {
17991
18876
  bitcoinProviderUrl: _bitcoinRpcUrl || this.config.bitcoinProviders[0].url
17992
18877
  },
17993
18878
  ...this.config.priceProviders && {
@@ -18052,7 +18937,7 @@ Error data: ${errorData || "none"}`
18052
18937
  if (typeof signatureBytes === "string" && signatureBytes.startsWith("{")) {
18053
18938
  try {
18054
18939
  const parsed = JSON.parse(signatureBytes);
18055
- signatureBytes = parsed.signature ?? (parsed.r && parsed.s && parsed.v ? import_ethers13.Signature.from({
18940
+ signatureBytes = parsed.signature ?? (parsed.r && parsed.s && parsed.v ? import_ethers14.Signature.from({
18056
18941
  r: parsed.r,
18057
18942
  s: parsed.s,
18058
18943
  v: parsed.v
@@ -18063,26 +18948,43 @@ Error data: ${errorData || "none"}`
18063
18948
  if (!signatureBytes.startsWith("0x")) {
18064
18949
  signatureBytes = "0x" + signatureBytes;
18065
18950
  }
18066
- const contractManager = this.getContractManager();
18067
- const positionManagerResult = contractManager.getPositionManager();
18068
- if (!positionManagerResult.success) {
18069
- throw new Error(
18070
- `Failed to get PositionManager: ${positionManagerResult.error.message}`
18071
- );
18072
- }
18073
- const positionManager = positionManagerResult.value;
18074
- await awaitSafeSubmissionWindow(Number(quantumTimestamp));
18075
- const extendPositionFn = positionManager["extendPosition"];
18076
- const tx = await extendPositionFn(
18951
+ const extendIface = new import_ethers14.Interface([
18952
+ "function extendPosition(bytes32 positionId, uint256 selectedTerm, uint256 quantumTimestamp, uint256 btcPrice, uint256 availableBTCBalance, uint256 proRataRenewalFee, bytes calldata extensionValidatorSignature) external returns (bool)"
18953
+ ]);
18954
+ const extendCalldata = extendIface.encodeFunctionData("extendPosition", [
18077
18955
  positionIdBytes32,
18078
18956
  BigInt(_selectedTerm),
18079
18957
  quantumTimestamp,
18080
18958
  btcPrice,
18081
18959
  availableBTCBalance,
18082
18960
  proRataRenewalFee,
18083
- signatureBytes,
18084
- { gasLimit: EXTEND_POSITION_GAS_CEILING }
18085
- );
18961
+ signatureBytes
18962
+ ]);
18963
+ const extendTo = this.getContractAddressesOrThrow().positionManager;
18964
+ const extendSigner = this.getSignerOrThrow();
18965
+ try {
18966
+ await assertSafeQuantumSubmission({
18967
+ provider: this.getProviderOrThrow(),
18968
+ to: extendTo,
18969
+ from: await extendSigner.getAddress(),
18970
+ data: extendCalldata,
18971
+ quantumTimestamp: Number(quantumTimestamp),
18972
+ onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
18973
+ });
18974
+ } catch (simError) {
18975
+ if (!(simError instanceof QuantumRevertError)) {
18976
+ throw simError;
18977
+ }
18978
+ throw new Error(
18979
+ `extendPosition would revert (pre-send simulation): ${simError.errorName ?? simError.selector ?? "unknown"}`
18980
+ );
18981
+ }
18982
+ const tx = await sendEip1559Transaction({
18983
+ signer: extendSigner,
18984
+ to: extendTo,
18985
+ data: extendCalldata,
18986
+ gasLimit: EXTEND_POSITION_GAS_CEILING
18987
+ });
18086
18988
  const txWithProvider = tx;
18087
18989
  if (!txWithProvider.provider) {
18088
18990
  const signer2 = this.getSignerOrThrow();
@@ -18595,7 +19497,7 @@ Error data: ${errorData || "none"}`
18595
19497
  ` Converting payment amount: ${request.paymentAmount} (type: ${typeof request.paymentAmount})`
18596
19498
  );
18597
19499
  }
18598
- const paymentAmountWei = (0, import_ethers13.parseEther)(
19500
+ const paymentAmountWei = (0, import_ethers14.parseEther)(
18599
19501
  request.paymentAmount.toString()
18600
19502
  );
18601
19503
  if (this.config.debug) {
@@ -18657,7 +19559,7 @@ Error data: ${errorData || "none"}`
18657
19559
  };
18658
19560
  }
18659
19561
  const ucdTokenAddress = await loanOps.ucdToken();
18660
- const ucdToken = new import_ethers13.Contract(
19562
+ const ucdToken = new import_ethers14.Contract(
18661
19563
  ucdTokenAddress,
18662
19564
  [
18663
19565
  "function balanceOf(address) view returns (uint256)",
@@ -18672,16 +19574,16 @@ Error data: ${errorData || "none"}`
18672
19574
  ucdToken["allowance"](signerAddress, positionManagerAddress)
18673
19575
  ]);
18674
19576
  if (this.config.debug) {
18675
- log.info(` UCD Balance: ${(0, import_ethers13.formatEther)(balance)} UCD`);
19577
+ log.info(` UCD Balance: ${(0, import_ethers14.formatEther)(balance)} UCD`);
18676
19578
  log.info(
18677
- ` UCD Allowance: ${(0, import_ethers13.formatEther)(allowance)} UCD`
19579
+ ` UCD Allowance: ${(0, import_ethers14.formatEther)(allowance)} UCD`
18678
19580
  );
18679
19581
  log.info(` Required: ${request.paymentAmount} UCD`);
18680
19582
  }
18681
19583
  if (balance < paymentAmountWei) {
18682
19584
  return {
18683
19585
  success: false,
18684
- error: `Insufficient UCD balance: ${(0, import_ethers13.formatEther)(
19586
+ error: `Insufficient UCD balance: ${(0, import_ethers14.formatEther)(
18685
19587
  balance
18686
19588
  )} UCD (need ${request.paymentAmount} UCD)`
18687
19589
  };
@@ -18751,9 +19653,10 @@ Error data: ${errorData || "none"}`
18751
19653
  rpcUrl: this.config.ethRpcUrl,
18752
19654
  // Pass the SDK's RPC URL to LIT Action
18753
19655
  ...request.rpcUrl && { rpcUrl: request.rpcUrl },
18754
- // Forward the configured Bitcoin provider so the validator reaches a reachable esplora on
18755
- // Hardhat (lit-ops maps it to the validator's devBitcoinProviderUrl); ignored off-hardhat.
18756
- ...this.config.bitcoinProviders?.[0]?.url && {
19656
+ // Hardhat-ONLY: gives the validator a reachable esplora (lit-ops maps it to the
19657
+ // validator's devBitcoinProviderUrl). Never emitted on prod chains, where the provider
19658
+ // comes from the on-chain BitcoinProviderRegistry and this field is server-ignored.
19659
+ ...this.config.chain === "hardhat" && this.config.bitcoinProviders?.[0]?.url && {
18757
19660
  bitcoinProviderUrl: this.config.bitcoinProviders[0].url
18758
19661
  },
18759
19662
  ...this.config.priceProviders && {
@@ -18942,7 +19845,6 @@ Error data: ${errorData || "none"}`
18942
19845
  quantumTimestamp: quantumTimestamp.toString()
18943
19846
  });
18944
19847
  }
18945
- await awaitSafeSubmissionWindow(Number(quantumTimestamp));
18946
19848
  const paymentAmountStr = paymentAmountWei.toString();
18947
19849
  const quantumTimestampStr = quantumTimestamp.toString();
18948
19850
  const btcPriceStr = btcPrice.toString();
@@ -18950,7 +19852,7 @@ Error data: ${errorData || "none"}`
18950
19852
  const paymentSigner = this.getSignerOrThrow();
18951
19853
  const paymentFrom = await paymentSigner.getAddress();
18952
19854
  const paymentProvider = contractManager.getProvider();
18953
- const paymentIface = new import_ethers13.Interface([
19855
+ const paymentIface = new import_ethers14.Interface([
18954
19856
  "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
18955
19857
  ]);
18956
19858
  const paymentCalldata = paymentIface.encodeFunctionData("makePayment", [
@@ -18960,58 +19862,30 @@ Error data: ${errorData || "none"}`
18960
19862
  btcPriceStr,
18961
19863
  signature
18962
19864
  ]);
18963
- const decodePaymentRevert = (e) => {
18964
- const { selector, name } = decodeQuantumRevert(e);
18965
- if (!selector)
18966
- return null;
18967
- return name ?? `Unknown error ${selector}`;
18968
- };
18969
- const MAX_DEADZONE_RESIMULATIONS = 3;
18970
- for (let sim = 1; ; sim++) {
18971
- try {
18972
- await paymentProvider.call({
18973
- to: paymentContractAddress,
18974
- from: paymentFrom,
18975
- data: paymentCalldata
18976
- });
18977
- break;
18978
- } catch (simError) {
18979
- const decoded = decodePaymentRevert(simError);
18980
- if (decoded === "DeadZoneViolation()" && sim <= MAX_DEADZONE_RESIMULATIONS) {
18981
- const nowSec = Math.floor(Date.now() / 1e3);
18982
- const sigQuantum = Math.floor(Number(quantumTimestamp) / 60) * 60;
18983
- const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
18984
- if (sigQuantum === realCurrentQuantum) {
18985
- if (this.config.debug) {
18986
- log.info(
18987
- "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
18988
- {}
18989
- );
18990
- }
18991
- break;
18992
- }
18993
- const gate = await awaitSafeSubmissionWindow(Number(quantumTimestamp));
18994
- if (gate.waited) {
18995
- if (this.config.debug) {
18996
- log.info(
18997
- `\u{1F501} Re-simulating makePayment after crossing the quantum boundary (attempt ${sim}/${MAX_DEADZONE_RESIMULATIONS})`,
18998
- {}
18999
- );
19000
- }
19001
- continue;
19002
- }
19003
- }
19004
- const label = decoded ?? simError?.reason ?? simError?.shortMessage ?? (simError instanceof Error ? simError.message : String(simError));
19005
- if (this.config.debug) {
19006
- log.error("\u274C makePayment pre-send simulation reverted", { error: label });
19007
- }
19008
- return {
19009
- success: false,
19010
- error: `Payment would revert (pre-send simulation): ${label}`,
19011
- positionId: request.positionId,
19012
- paymentAmountUCD: request.paymentAmount
19013
- };
19865
+ try {
19866
+ await assertSafeQuantumSubmission({
19867
+ provider: paymentProvider,
19868
+ to: paymentContractAddress,
19869
+ from: paymentFrom,
19870
+ data: paymentCalldata,
19871
+ quantumTimestamp: Number(quantumTimestamp),
19872
+ onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
19873
+ });
19874
+ } catch (simError) {
19875
+ if (!(simError instanceof QuantumRevertError)) {
19876
+ throw simError;
19877
+ }
19878
+ const simCause = simError.cause;
19879
+ const label = simError.errorName ?? (simError.selector ? `Unknown error ${simError.selector}` : null) ?? simCause?.reason ?? simCause?.shortMessage ?? (simCause instanceof Error ? simCause.message : String(simCause));
19880
+ if (this.config.debug) {
19881
+ log.error("\u274C makePayment pre-send simulation reverted", { error: label });
19014
19882
  }
19883
+ return {
19884
+ success: false,
19885
+ error: `Payment would revert (pre-send simulation): ${label}`,
19886
+ positionId: request.positionId,
19887
+ paymentAmountUCD: request.paymentAmount
19888
+ };
19015
19889
  }
19016
19890
  const tx = await sendEip1559Transaction({
19017
19891
  signer: paymentSigner,
@@ -19294,7 +20168,7 @@ Error data: ${errorData || "none"}`
19294
20168
  const pkpId = position.pkpId;
19295
20169
  if (litValidatorAddr && pkpId) {
19296
20170
  try {
19297
- const litValidator = new import_ethers13.Contract(
20171
+ const litValidator = new import_ethers14.Contract(
19298
20172
  litValidatorAddr,
19299
20173
  ["function pkpOwners(bytes32) view returns (address)"],
19300
20174
  this.getProviderOrThrow()
@@ -19302,7 +20176,7 @@ Error data: ${errorData || "none"}`
19302
20176
  const owner = await litValidator.pkpOwners(
19303
20177
  pkpId
19304
20178
  );
19305
- if (owner && owner !== import_ethers13.ZeroAddress) {
20179
+ if (owner && owner !== import_ethers14.ZeroAddress) {
19306
20180
  controller = owner;
19307
20181
  }
19308
20182
  } catch {
@@ -19354,7 +20228,7 @@ Error data: ${errorData || "none"}`
19354
20228
  pkpNftAddress,
19355
20229
  this.getChipotlePublicKeyFallback()
19356
20230
  );
19357
- pkpEthAddress = (0, import_ethers13.computeAddress)(pkpPublicKey);
20231
+ pkpEthAddress = (0, import_ethers14.computeAddress)(pkpPublicKey);
19358
20232
  pkpCache.set(positionId, {
19359
20233
  publicKey: pkpPublicKey,
19360
20234
  ethAddress: pkpEthAddress,
@@ -19685,13 +20559,6 @@ Error data: ${errorData || "none"}`
19685
20559
  error: "PositionManager address not configured"
19686
20560
  };
19687
20561
  }
19688
- const positionManagerContract = new import_ethers13.Contract(
19689
- positionManagerAddress,
19690
- [
19691
- "function withdrawBTC((bytes32 positionId, bytes32 actionHash, bytes32 authorizedSpendsHash, bytes32 ucdDebtHash, bytes32 contractBundleHash, string withdrawalAddress, uint256 totalDeduction, uint256 newCollateral, uint256 quantumTimestamp, uint256 btcPrice, string utxoTxid, uint32 utxoVout) params, bytes withdrawalValidatorSignature, bytes btcSpendAuthSignature) external returns (bool)"
19692
- ],
19693
- this.getSignerOrThrow()
19694
- );
19695
20562
  const withdrawalParams = {
19696
20563
  positionId: positionIdBytes32,
19697
20564
  actionHash: validationResponse.actionHash,
@@ -19724,86 +20591,56 @@ Error data: ${errorData || "none"}`
19724
20591
  utxoVout: withdrawalParams.utxoVout
19725
20592
  });
19726
20593
  }
19727
- await awaitSafeSubmissionWindow(Number(withdrawalParams.quantumTimestamp));
20594
+ const withdrawIface = new import_ethers14.Interface([
20595
+ "function withdrawBTC((bytes32 positionId, bytes32 actionHash, bytes32 authorizedSpendsHash, bytes32 ucdDebtHash, bytes32 contractBundleHash, string withdrawalAddress, uint256 totalDeduction, uint256 newCollateral, uint256 quantumTimestamp, uint256 btcPrice, string utxoTxid, uint32 utxoVout) params, bytes withdrawalValidatorSignature, bytes btcSpendAuthSignature) external returns (bool)"
20596
+ ]);
20597
+ const withdrawCalldata = withdrawIface.encodeFunctionData("withdrawBTC", [
20598
+ withdrawalParams,
20599
+ signatureBytes,
20600
+ btcSpendAuthBytes
20601
+ ]);
20602
+ const withdrawSigner = this.getSignerOrThrow();
19728
20603
  let tx;
19729
20604
  try {
19730
- tx = await positionManagerContract["withdrawBTC"](
19731
- withdrawalParams,
19732
- signatureBytes,
19733
- btcSpendAuthBytes,
19734
- { gasLimit: WITHDRAW_BTC_GAS_CEILING }
19735
- );
20605
+ await assertSafeQuantumSubmission({
20606
+ provider: this.getProviderOrThrow(),
20607
+ to: positionManagerAddress,
20608
+ from: await withdrawSigner.getAddress(),
20609
+ data: withdrawCalldata,
20610
+ quantumTimestamp: Number(withdrawalParams.quantumTimestamp),
20611
+ onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
20612
+ });
20613
+ tx = await sendEip1559Transaction({
20614
+ signer: withdrawSigner,
20615
+ to: positionManagerAddress,
20616
+ data: withdrawCalldata,
20617
+ gasLimit: WITHDRAW_BTC_GAS_CEILING
20618
+ });
19736
20619
  } catch (txError) {
19737
- const txErrorMsg = txError instanceof Error ? txError.message : String(txError);
20620
+ const txErrorMsg = txError instanceof QuantumRevertError ? `Withdrawal would revert (pre-send simulation): ${txError.errorName ?? txError.selector ?? "unknown"}` : txError instanceof Error ? txError.message : String(txError);
19738
20621
  if (this.config.debug) {
19739
- log.warn(
19740
- "\u26A0\uFE0F withdrawBTC contract interface failed, falling back to raw transaction",
19741
- { error: txErrorMsg }
19742
- );
20622
+ log.error("\u274C withdrawBTC transaction submission failed", {
20623
+ error: txErrorMsg,
20624
+ withdrawalParams,
20625
+ signatureBytesLength: signatureBytes.length
20626
+ });
19743
20627
  }
19744
20628
  try {
19745
- const contractAddress = this.getContractAddressesOrThrow().positionManager;
19746
- const iface = new import_ethers13.Interface([
19747
- "function withdrawBTC((bytes32 positionId, bytes32 actionHash, bytes32 authorizedSpendsHash, bytes32 ucdDebtHash, bytes32 contractBundleHash, string withdrawalAddress, uint256 totalDeduction, uint256 newCollateral, uint256 quantumTimestamp, uint256 btcPrice, string utxoTxid, uint32 utxoVout) params, bytes withdrawalValidatorSignature, bytes btcSpendAuthSignature) external returns (bool)"
19748
- ]);
19749
- const calldata = iface.encodeFunctionData("withdrawBTC", [
19750
- withdrawalParams,
19751
- signatureBytes,
19752
- btcSpendAuthBytes
19753
- ]);
19754
- const signer = this.getSignerOrThrow();
19755
- const fromAddress = await signer.getAddress();
19756
- const estimatedGas = await estimateContractCallGasWithMargin(
19757
- this.getProviderOrThrow(),
19758
- fromAddress,
19759
- contractAddress,
19760
- calldata,
19761
- MINT_UCD_ESTIMATE_MARGIN_BPS,
19762
- WITHDRAW_BTC_GAS_CEILING
19763
- );
19764
- const gasLimit = estimatedGas ?? WITHDRAW_BTC_GAS_CEILING;
19765
- await awaitSafeSubmissionWindow(
19766
- Number(withdrawalParams.quantumTimestamp)
20629
+ const position = await this.getPosition(positionId);
20630
+ const btcAmount = (Number(withdrawalAmount) / 1e8).toFixed(8);
20631
+ const message = formatBTCWithdrawalFailureMessage(
20632
+ btcAmount,
20633
+ position?.vaultAddress || "unknown",
20634
+ withdrawalAddress,
20635
+ positionId
19767
20636
  );
19768
- tx = await signer.sendTransaction({
19769
- to: contractAddress,
19770
- data: calldata,
19771
- value: "0x0",
19772
- gasLimit
19773
- });
19774
- if (this.config.debug) {
19775
- log.info("\u2705 withdrawBTC raw transaction fallback succeeded", {
19776
- txHash: tx.hash,
19777
- gasLimit: gasLimit.toString(),
19778
- estimated: estimatedGas !== null
19779
- });
19780
- }
19781
- } catch (rawError) {
19782
- const rawErrorMsg = rawError instanceof Error ? rawError.message : String(rawError);
19783
- if (this.config.debug) {
19784
- log.error("\u274C withdrawBTC transaction failed (raw fallback also failed)", {
19785
- error: rawErrorMsg,
19786
- withdrawalParams,
19787
- signatureBytesLength: signatureBytes.length
19788
- });
19789
- }
19790
- try {
19791
- const position = await this.getPosition(positionId);
19792
- const btcAmount = (Number(withdrawalAmount) / 1e8).toFixed(8);
19793
- const message = formatBTCWithdrawalFailureMessage(
19794
- btcAmount,
19795
- position?.vaultAddress || "unknown",
19796
- withdrawalAddress,
19797
- positionId
19798
- );
19799
- await this._relayNotification(message);
19800
- } catch {
19801
- }
19802
- return {
19803
- success: false,
19804
- error: `Transaction submission failed: ${rawErrorMsg}`
19805
- };
20637
+ await this._relayNotification(message);
20638
+ } catch {
19806
20639
  }
20640
+ return {
20641
+ success: false,
20642
+ error: `Transaction submission failed: ${txErrorMsg}`
20643
+ };
19807
20644
  }
19808
20645
  if (this.config.debug) {
19809
20646
  log.info("\u2705 Transaction submitted, waiting for confirmation:", {
@@ -19826,6 +20663,12 @@ Error data: ${errorData || "none"}`
19826
20663
  error: `Transaction confirmation failed: ${waitErrorMsg}`
19827
20664
  };
19828
20665
  }
20666
+ if (!receipt) {
20667
+ return {
20668
+ success: false,
20669
+ error: `Transaction confirmation returned no receipt. Hash: ${tx.hash}`
20670
+ };
20671
+ }
19829
20672
  if (this.config.debug) {
19830
20673
  log.info("\u2705 BTC withdrawal confirmed", {
19831
20674
  blockNumber: receipt.blockNumber,
@@ -19941,7 +20784,8 @@ Error data: ${errorData || "none"}`
19941
20784
  targetAmount: request.targetAmount,
19942
20785
  networkFee: request.networkFee,
19943
20786
  chainId,
19944
- signer
20787
+ signer,
20788
+ ...request.utxos ? { utxos: request.utxos } : {}
19945
20789
  });
19946
20790
  const headers = {
19947
20791
  "Content-Type": "application/json",
@@ -19972,7 +20816,10 @@ Error data: ${errorData || "none"}`
19972
20816
  userSignature: envelope.userSignature,
19973
20817
  borrowerAddress: envelope.borrowerAddress,
19974
20818
  chainId: envelope.chainId,
19975
- timestamp: envelope.timestamp
20819
+ timestamp: envelope.timestamp,
20820
+ // P6/#8: the exact input set hashed into the user signature.
20821
+ // The server must forward this set verbatim to the signer.
20822
+ utxos: envelope.utxos
19976
20823
  })
19977
20824
  }
19978
20825
  );
@@ -20093,7 +20940,7 @@ Error data: ${errorData || "none"}`
20093
20940
  const btcSpendAuthorizerAbi = [
20094
20941
  "function getAuthorizedSpends(bytes32) view returns (tuple(string txid, uint32 vout, uint256 satoshis, string targetAddress, uint256 targetAmount, uint256 authorizedAt)[])"
20095
20942
  ];
20096
- const btcSpendAuthorizer = new import_ethers13.Contract(
20943
+ const btcSpendAuthorizer = new import_ethers14.Contract(
20097
20944
  btcSpendAuthorizerAddress,
20098
20945
  btcSpendAuthorizerAbi,
20099
20946
  this.getProviderOrThrow()
@@ -20179,7 +21026,7 @@ Error data: ${errorData || "none"}`
20179
21026
  const abi = [
20180
21027
  "function getAuthorizedSpends(bytes32) view returns (tuple(string txid, uint32 vout, uint256 satoshis, string targetAddress, uint256 targetAmount, uint256 authorizedAt)[])"
20181
21028
  ];
20182
- const contract = new import_ethers13.Contract(
21029
+ const contract = new import_ethers14.Contract(
20183
21030
  btcSpendAuthorizerAddress,
20184
21031
  abi,
20185
21032
  this.getProviderOrThrow()
@@ -20193,7 +21040,7 @@ Error data: ${errorData || "none"}`
20193
21040
  targetAddress: spend.targetAddress,
20194
21041
  targetAmount: Number(spend.targetAmount),
20195
21042
  authorizedAt: Number(spend.authorizedAt),
20196
- utxoKey: (0, import_ethers13.solidityPackedKeccak256)(
21043
+ utxoKey: (0, import_ethers14.solidityPackedKeccak256)(
20197
21044
  ["string", "uint32"],
20198
21045
  [spend.txid, Number(spend.vout)]
20199
21046
  )
@@ -20312,10 +21159,13 @@ Error data: ${errorData || "none"}`
20312
21159
  }
20313
21160
  },
20314
21161
  rpcUrl,
20315
- ...sdkBitcoinProviderUrl && { bitcoinProviderUrl: sdkBitcoinProviderUrl },
21162
+ // Hardhat-ONLY: never emitted on prod chains (server-ignored there; provider comes
21163
+ // from the on-chain BitcoinProviderRegistry). The stale-spend action reads
21164
+ // `devBitcoinProviderUrl` directly on Hardhat — this legacy alias is redundant but
21165
+ // kept behind the Hardhat gate so no prod request carries the caller-looking field.
21166
+ ...this.config.chain === "hardhat" && sdkBitcoinProviderUrl && { bitcoinProviderUrl: sdkBitcoinProviderUrl },
20316
21167
  // Hardhat (chainId 1337/31337) has no on-chain BitcoinProviderRegistry —
20317
- // Lit Actions read `devBitcoinProviderUrl` from globalThis. Always pass;
20318
- // non-hardhat chains ignore it. (Single source of truth: bitcoinProviderUrl.)
21168
+ // Lit Actions read `devBitcoinProviderUrl` from globalThis. Non-hardhat chains ignore it.
20319
21169
  ...sdkBitcoinProviderUrl && { devBitcoinProviderUrl: sdkBitcoinProviderUrl },
20320
21170
  ...params.minConfirmations !== void 0 && {
20321
21171
  minConfirmations: params.minConfirmations
@@ -20329,7 +21179,7 @@ Error data: ${errorData || "none"}`
20329
21179
  error: attRes.error ?? attRes.reason ?? `LIT recovery attestation rejected${attRes.failedStep ? ` at step ${attRes.failedStep}` : ""}`
20330
21180
  };
20331
21181
  }
20332
- const btcSpendAuthorizer = new import_ethers13.Contract(
21182
+ const btcSpendAuthorizer = new import_ethers14.Contract(
20333
21183
  btcSpendAuthorizerAddress,
20334
21184
  [
20335
21185
  "function cancelStaleSpendWithProof(bytes32 positionId, bytes32 utxoKey, uint256 authorizedAt, string calldata invalidatorTxid, uint256 attestationTimestamp, bytes calldata litSignature) external"
@@ -20638,6 +21488,15 @@ Error data: ${errorData || "none"}`
20638
21488
  async getLoanEvents(positionId, filter) {
20639
21489
  return this.loanQuery.getLoanEvents(positionId, filter);
20640
21490
  }
21491
+ /**
21492
+ * Get loan-event history for every position owned by a wallet.
21493
+ * Composes getUserPositions + getLoanEvents; returns one LoanEvents per
21494
+ * position (each carrying its own positionId). Bitcoin deposits are not on the
21495
+ * subgraph — merge those in from an Esplora source on the consumer side.
21496
+ */
21497
+ async getWalletLoanEvents(userAddress, filter) {
21498
+ return this.loanQuery.getWalletLoanEvents(userAddress, filter);
21499
+ }
20641
21500
  /**
20642
21501
  * Get protocol-wide events across all indexed entity types.
20643
21502
  * Returns a merged, sorted discriminated-union timeline.
@@ -20724,10 +21583,10 @@ Error data: ${errorData || "none"}`
20724
21583
  "function allowance(address owner, address spender) view returns (uint256)",
20725
21584
  "function approve(address spender, uint256 amount) returns (bool)"
20726
21585
  ];
20727
- const stablecoin = new import_ethers13.Contract(params.stablecoinAddress, erc20Abi, params.signer);
21586
+ const stablecoin = new import_ethers14.Contract(params.stablecoinAddress, erc20Abi, params.signer);
20728
21587
  const allowance = await stablecoin.allowance(signerAddress, psmAddress);
20729
21588
  if (allowance < params.amountWei) {
20730
- const approveTx = await stablecoin.approve(psmAddress, import_ethers13.MaxUint256);
21589
+ const approveTx = await stablecoin.approve(psmAddress, import_ethers14.MaxUint256);
20731
21590
  await approveTx.wait();
20732
21591
  }
20733
21592
  const psm = SimplePSMV2__factory.connect(psmAddress, params.signer);
@@ -20762,10 +21621,10 @@ Error data: ${errorData || "none"}`
20762
21621
  "function allowance(address owner, address spender) view returns (uint256)",
20763
21622
  "function approve(address spender, uint256 amount) returns (bool)"
20764
21623
  ];
20765
- const ucdToken = new import_ethers13.Contract(ucdAddress, erc20Abi, params.signer);
21624
+ const ucdToken = new import_ethers14.Contract(ucdAddress, erc20Abi, params.signer);
20766
21625
  const allowance = await ucdToken.allowance(signerAddress, ucdControllerAddress);
20767
21626
  if (allowance < params.ucdAmountWei) {
20768
- const approveTx = await ucdToken.approve(ucdControllerAddress, import_ethers13.MaxUint256);
21627
+ const approveTx = await ucdToken.approve(ucdControllerAddress, import_ethers14.MaxUint256);
20769
21628
  await approveTx.wait();
20770
21629
  }
20771
21630
  const psm = SimplePSMV2__factory.connect(psmAddress, params.signer);
@@ -20977,7 +21836,7 @@ Error data: ${errorData || "none"}`
20977
21836
  const maxUcdWhole = BigInt(result[3].toString());
20978
21837
  return success({
20979
21838
  liquidationThreshold: result[0].toString(),
20980
- minimumLoanValueUcd: (0, import_ethers13.formatUnits)(result[2], 18),
21839
+ minimumLoanValueUcd: (0, import_ethers14.formatUnits)(result[2], 18),
20981
21840
  minimumLoanValueWei: result[2].toString(),
20982
21841
  maxSingleLoanValueUcd: maxUcdWhole.toString(),
20983
21842
  maxSingleLoanValueWei: (maxUcdWhole * 10n ** 18n).toString()
@@ -21014,7 +21873,7 @@ Error data: ${errorData || "none"}`
21014
21873
  const balanceWei = await provider.getBalance(address);
21015
21874
  return success({
21016
21875
  balanceWei: balanceWei.toString(),
21017
- balanceEth: (0, import_ethers13.formatEther)(balanceWei)
21876
+ balanceEth: (0, import_ethers14.formatEther)(balanceWei)
21018
21877
  });
21019
21878
  } catch (error) {
21020
21879
  return failure(new SDKError({
@@ -21094,6 +21953,38 @@ Error data: ${errorData || "none"}`
21094
21953
  }));
21095
21954
  }
21096
21955
  }
21956
+ /**
21957
+ * List a vault address's PENDING (not-yet-confirmed) incoming BTC deposits.
21958
+ *
21959
+ * A deposit is "pending" until it reaches the protocol's minimum confirmation
21960
+ * depth (6) — the confirmed balance endpoints (`getVaultBalance` /
21961
+ * `getAddressBalance`) omit anything shallower, so this surfaces in-flight
21962
+ * deposits (0–5 confirmations). Server-delegated: lit-ops-server queries its
21963
+ * configured premium Esplora provider (`/api/lit/pending-deposits`). Requires
21964
+ * service mode.
21965
+ */
21966
+ async getPendingDeposits(vaultAddress) {
21967
+ this.ensureInitialized();
21968
+ if (!isServiceModeConfig(this.config)) {
21969
+ throw new Error("getPendingDeposits requires service mode \u2014 standalone mode is not supported");
21970
+ }
21971
+ const trimmedAddress = vaultAddress?.trim();
21972
+ if (!trimmedAddress) {
21973
+ throw new Error("vaultAddress is required");
21974
+ }
21975
+ const serviceEndpoint = this.config.serviceEndpoint;
21976
+ const url = `${serviceEndpoint}/api/lit/pending-deposits?vaultAddress=${encodeURIComponent(trimmedAddress)}`;
21977
+ const response = await fetch(url, {
21978
+ headers: await this.getAuthHeader()
21979
+ });
21980
+ if (!response.ok) {
21981
+ throw new Error(`Pending deposits request failed: ${response.status} ${response.statusText}`);
21982
+ }
21983
+ const json = await response.json();
21984
+ if (!json.success)
21985
+ throw new Error(json.error ?? "Pending deposits request failed");
21986
+ return json.data ?? [];
21987
+ }
21097
21988
  // ============================================================================
21098
21989
  // HIGH-LEVEL API - Mock Token Operations (Test Networks Only)
21099
21990
  // ============================================================================
@@ -21353,7 +22244,7 @@ Error data: ${errorData || "none"}`
21353
22244
  * Convert decimal position ID to bytes32 format
21354
22245
  */
21355
22246
  toBytes32(value) {
21356
- return (0, import_ethers13.zeroPadValue)((0, import_ethers13.toBeHex)(BigInt(value)), 32);
22247
+ return (0, import_ethers14.zeroPadValue)((0, import_ethers14.toBeHex)(BigInt(value)), 32);
21357
22248
  }
21358
22249
  /**
21359
22250
  * Check if an error indicates a technical failure vs business logic rejection
@@ -21394,7 +22285,7 @@ Error data: ${errorData || "none"}`
21394
22285
  const coreModuleAbi = [
21395
22286
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
21396
22287
  ];
21397
- const coreModule = new import_ethers13.Contract(
22288
+ const coreModule = new import_ethers14.Contract(
21398
22289
  coreModuleAddress,
21399
22290
  coreModuleAbi,
21400
22291
  provider
@@ -21461,7 +22352,7 @@ Error data: ${errorData || "none"}`
21461
22352
  const corePositionAbi = [
21462
22353
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
21463
22354
  ];
21464
- const corePositionContract = new import_ethers13.Contract(
22355
+ const corePositionContract = new import_ethers14.Contract(
21465
22356
  coreAddress,
21466
22357
  corePositionAbi,
21467
22358
  provider
@@ -21472,7 +22363,7 @@ Error data: ${errorData || "none"}`
21472
22363
  positionIdBytes32
21473
22364
  );
21474
22365
  const positionIdFromCore = corePosition?.positionId || corePosition?.[0] || null;
21475
- if (positionIdFromCore && positionIdFromCore !== import_ethers13.ZeroHash && positionIdFromCore !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
22366
+ if (positionIdFromCore && positionIdFromCore !== import_ethers14.ZeroHash && positionIdFromCore !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
21476
22367
  if (this.config.debug) {
21477
22368
  log.info("Position exists in core contract", {
21478
22369
  positionId,
@@ -21540,7 +22431,7 @@ Error data: ${errorData || "none"}`
21540
22431
  throw new Error("loanOperationsManager address not configured");
21541
22432
  }
21542
22433
  const runner = this.config.contractSigner || this.getProviderOrThrow();
21543
- const contract = new import_ethers13.Contract(
22434
+ const contract = new import_ethers14.Contract(
21544
22435
  addr.loanOperationsManager,
21545
22436
  [
21546
22437
  "function getProtocolConfig() external view returns (uint256 liquidationThreshold, uint256 minimumLoanValueUcd, uint256 minimumLoanValueWei, uint256 maxSingleLoanValueUcd)"
@@ -21553,7 +22444,7 @@ Error data: ${errorData || "none"}`
21553
22444
  const maxUcdWhole = BigInt(result[3].toString());
21554
22445
  return {
21555
22446
  liquidationThreshold: result[0].toString(),
21556
- minimumLoanValueUcd: (0, import_ethers13.formatUnits)(result[2], 18),
22447
+ minimumLoanValueUcd: (0, import_ethers14.formatUnits)(result[2], 18),
21557
22448
  minimumLoanValueWei: result[2].toString(),
21558
22449
  maxSingleLoanValueUcd: maxUcdWhole.toString(),
21559
22450
  maxSingleLoanValueWei: (maxUcdWhole * 10n ** 18n).toString()
@@ -21571,8 +22462,8 @@ Error data: ${errorData || "none"}`
21571
22462
  const MIN_REVEAL_DELAY = 60;
21572
22463
  const MAX_RANDOM_DELAY = 240;
21573
22464
  const entropyInput = positionId + quantumTimestamp.toString();
21574
- const hash = (0, import_ethers13.keccak256)(
21575
- (0, import_ethers13.toUtf8Bytes)(entropyInput)
22465
+ const hash = (0, import_ethers14.keccak256)(
22466
+ (0, import_ethers14.toUtf8Bytes)(entropyInput)
21576
22467
  );
21577
22468
  const randomValue = BigInt(hash);
21578
22469
  const randomDelay = Number(randomValue % BigInt(MAX_RANDOM_DELAY));
@@ -21589,6 +22480,20 @@ Error data: ${errorData || "none"}`
21589
22480
  if (this.config.debug) {
21590
22481
  log.info(`\u{1F310} Calling liquidation server endpoint: ${endpoint}`);
21591
22482
  }
22483
+ const signer = this.getSignerOrThrow();
22484
+ const intentSigner = await signer.getAddress();
22485
+ const chainId = Number(
22486
+ (await this.getProviderOrThrow().getNetwork()).chainId
22487
+ );
22488
+ const rawPositionId = /^0x/i.test(params.positionId.trim()) ? params.positionId.trim().slice(2) : params.positionId.trim();
22489
+ const canonicalPositionId = "0x" + rawPositionId.padStart(64, "0").toLowerCase();
22490
+ const intentTimestamp = Math.floor(Date.now() / 1e3);
22491
+ const intentActionHash = (0, import_ethers14.keccak256)((0, import_ethers14.toUtf8Bytes)("liquidate-position"));
22492
+ const intentHash = (0, import_ethers14.solidityPackedKeccak256)(
22493
+ ["bytes32", "uint256", "uint256", "address", "bytes32"],
22494
+ [canonicalPositionId, intentTimestamp, chainId, intentSigner, intentActionHash]
22495
+ );
22496
+ const intentSignature = await signer.signMessage((0, import_ethers14.getBytes)(intentHash));
21592
22497
  const response = await fetch(endpoint, {
21593
22498
  method: "POST",
21594
22499
  headers: { "Content-Type": "application/json" },
@@ -21604,6 +22509,11 @@ Error data: ${errorData || "none"}`
21604
22509
  UCDController: params.contractAddresses.ucdController,
21605
22510
  LiquidationManagerModule: params.contractAddresses.liquidationManager
21606
22511
  },
22512
+ liquidatorIntent: {
22513
+ timestamp: intentTimestamp,
22514
+ chainId,
22515
+ signature: intentSignature
22516
+ },
21607
22517
  rpcUrl: params.rpcUrl || this.config.ethRpcUrl,
21608
22518
  priceProviders: this.config.priceProviders,
21609
22519
  pkpTokenId: params.pkpData.tokenId,
@@ -21635,7 +22545,7 @@ Error data: ${errorData || "none"}`
21635
22545
  const vrfSeedRaw = await lmResult.value["vrfSeeds"](positionId);
21636
22546
  const vrfSeed = BigInt(vrfSeedRaw.toString());
21637
22547
  if (vrfSeed !== 0n) {
21638
- const finalEntropy = (0, import_ethers13.solidityPackedKeccak256)(
22548
+ const finalEntropy = (0, import_ethers14.solidityPackedKeccak256)(
21639
22549
  ["uint256", "uint256", "bytes32"],
21640
22550
  [vrfSeed, BigInt(quantumTimestamp.toString()), positionId]
21641
22551
  );
@@ -21913,14 +22823,14 @@ var EventHelpers = {
21913
22823
  };
21914
22824
 
21915
22825
  // src/utils/position-delegate.utils.ts
21916
- var import_ethers14 = require("ethers");
22826
+ var import_ethers15 = require("ethers");
21917
22827
  var POSITION_DELEGATE_REGISTRY_ABI = [
21918
22828
  "function positionDelegate(bytes32 positionId) view returns (address)",
21919
22829
  "function setPositionDelegate(bytes32 positionId, address newDelegate) external",
21920
22830
  "event PositionDelegateUpdated(bytes32 indexed positionId, address indexed oldDelegate, address indexed newDelegate)"
21921
22831
  ];
21922
22832
  async function setPositionDelegate(positionId, delegate, signer, registryAddress) {
21923
- const registry = new import_ethers14.Contract(
22833
+ const registry = new import_ethers15.Contract(
21924
22834
  registryAddress,
21925
22835
  POSITION_DELEGATE_REGISTRY_ABI,
21926
22836
  signer
@@ -21928,7 +22838,7 @@ async function setPositionDelegate(positionId, delegate, signer, registryAddress
21928
22838
  return registry["setPositionDelegate"](positionId, delegate);
21929
22839
  }
21930
22840
  async function getPositionDelegate(positionId, provider, registryAddress) {
21931
- const registry = new import_ethers14.Contract(
22841
+ const registry = new import_ethers15.Contract(
21932
22842
  registryAddress,
21933
22843
  POSITION_DELEGATE_REGISTRY_ABI,
21934
22844
  provider