@gvnrdao/dh-lit-actions 0.0.193 → 0.0.194

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.
Files changed (36) hide show
  1. package/dist/admin-liquidation-validator.action.js +1 -1
  2. package/dist/admin-liquidation-validator.hash +1 -1
  3. package/dist/admin-liquidation-validator.meta.json +5 -5
  4. package/dist/always-signer.meta.json +1 -1
  5. package/dist/authorization-dummy-b.meta.json +1 -1
  6. package/dist/authorization-dummy.meta.json +1 -1
  7. package/dist/btc-transaction-signer.meta.json +1 -1
  8. package/dist/btc-withdrawal.action.js +1 -1
  9. package/dist/btc-withdrawal.hash +1 -1
  10. package/dist/btc-withdrawal.meta.json +5 -5
  11. package/dist/extend-position-validator.action.js +1 -1
  12. package/dist/extend-position-validator.hash +1 -1
  13. package/dist/extend-position-validator.meta.json +5 -5
  14. package/dist/liquidation-validator.action.js +1 -1
  15. package/dist/liquidation-validator.hash +1 -1
  16. package/dist/liquidation-validator.meta.json +5 -5
  17. package/dist/loan-vault-btc-balance.action.js +1 -1
  18. package/dist/loan-vault-btc-balance.hash +1 -1
  19. package/dist/loan-vault-btc-balance.meta.json +5 -5
  20. package/dist/pkp-validator-datil.meta.json +1 -1
  21. package/dist/price-oracle.meta.json +1 -1
  22. package/dist/process-payment-sign-only.meta.json +1 -1
  23. package/dist/process-payment-validator.meta.json +1 -1
  24. package/dist/ucd-mint-validator.action.js +1 -1
  25. package/dist/ucd-mint-validator.hash +1 -1
  26. package/dist/ucd-mint-validator.meta.json +5 -5
  27. package/package.json +1 -1
  28. package/pkg-dist/pkg-src/constants/chunks/lit-actions-registry.js +39 -39
  29. package/pkg-dist/pkg-src/constants/chunks/lit-actions-registry.js.map +1 -1
  30. package/pkg-dist/src/constants/chunks/bitcoin-network-config.d.ts +4 -4
  31. package/pkg-dist/src/constants/chunks/bitcoin-network-config.js +4 -4
  32. package/pkg-dist/src/modules/vault-balance.module.d.ts.map +1 -1
  33. package/pkg-dist/src/modules/vault-balance.module.js +4 -5
  34. package/pkg-dist/src/modules/vault-balance.module.js.map +1 -1
  35. package/pkg-dist/src/modules/vault-snapshot.js +2 -5
  36. package/pkg-dist/src/modules/vault-snapshot.js.map +1 -1
@@ -1 +1 @@
1
- var _LIT_ACTION_=(()=>{var t=100000000n,e=1000000000000000000n,o=100;var r=(t=>(t.SEPOLIA="sepolia",t.ETHEREUM="ethereum",t.HARDHAT="hardhat",t))(r||{}),i=(t=>(t.TESTNET="testnet",t.MAINNET="mainnet",t.REGTEST="regtest",t))(i||{}),n={sepolia:"testnet",ethereum:"mainnet",hardhat:"regtest"},a={ethereum:1,sepolia:11155111,hardhat:31337},s={regtest:[{url:"https://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"regtest",minConfirmations:1,name:"Diamond Hands"},{url:"http://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"regtest",minConfirmations:1,name:"Diamond Hands"},{url:"http://127.0.0.1:18443",network:"regtest",minConfirmations:1,name:"Local Regtest"},{url:"https://313561dc30c5.ngrok-free.app",network:"regtest",minConfirmations:1,name:"Ngrok Bitcoin Tunnel"}],testnet:[{url:"https://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"testnet",minConfirmations:1,name:"Diamond Hands"},{url:"http://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"testnet",minConfirmations:1,name:"Diamond Hands"}],mainnet:[{url:"https://blockstream.info/api",network:"mainnet",minConfirmations:6,name:"Blockstream Mainnet"},{url:"https://mempool.space/api",network:"mainnet",minConfirmations:6,name:"Mempool.space Mainnet"}]};function c(t){const e=t.toLowerCase().trim();if("sepolia"===e)return"sepolia";if("ethereum"===e||"mainnet"===e)return"ethereum";if("hardhat"===e)return"hardhat";throw new Error(`Unsupported EVM chain: "${t}". Supported chains: ${Object.values(r).join(", ")}`)}function l(t){return n[t]}function u(t){return a[t]}function d(t){return Math.floor(t/o)*o}function h(){return d(Math.floor(Date.now()/1e3))}async function p(){const t=Math.floor(Date.now()/1e3),e=t%o;if(!function(t){const e=(t??Math.floor(Date.now()/1e3))%o;return e>=92||e<8}(t))return;let r;r=e>=92?100-e+8:8-e,console.log(`[Quantum] Waiting ${r}s for safe moment (current time in quantum: ${e}s)`),await new Promise((t=>setTimeout(t,1e3*r))),console.log(`[Quantum] Safe quantum ready: ${h()}`)}0,0;var m=class{static verifyAmountPositionActionAuthorizationStructure(t){if(!t||"object"!=typeof t)throw new Error('Missing or invalid "auth" parameter - must be an object');if(!t.positionId)throw new Error("auth.positionId is required");if("number"!=typeof t.chainId)throw new Error("auth.chainId must be a number");if("number"!=typeof t.timestamp)throw new Error("auth.timestamp must be a number");if(void 0===t.amount||null===t.amount)throw new Error("auth.amount is required");if(!t.action)throw new Error("auth.action is required");if(!t.signature)throw new Error("auth.signature is required");if(!t.mode)throw new Error("auth.mode is required");if("dev"!==t.mode&&"prod"!==t.mode)throw new Error(`auth.mode must be "dev" or "prod", got: ${t.mode}`);return t}static async recoverSigner(t,e){try{const o=ethers.utils.arrayify(t),r=ethers.utils.hashMessage(o);return ethers.utils.recoverAddress(r,e)}catch(t){throw console.error("[Authorization] Signature recovery failed:",t),new Error(`Failed to recover signer: ${t instanceof Error?t.message:String(t)}`)}}static async verifyActionAuthorization(t,e,o){await p();try{!function(t,e){const o=d(e??Math.floor(Date.now()/1e3)),r=d(t);if(r!==o)throw new Error(`[Quantum] Invalid timestamp: signature from quantum ${r}, current quantum is ${o}`)}(t.timestamp)}catch(t){return console.error("[Authorization] Quantum timestamp validation failed:",t),!1}if(!t.mode)return console.error("[Authorization] Missing mode field"),!1;if("dev"!==t.mode&&"prod"!==t.mode)return console.error(`[Authorization] Invalid mode: ${t.mode}, must be "dev" or "prod"`),!1;if("prod"===t.mode)return console.error('[Authorization] Production mode not yet supported - use mode="dev"'),!1;const r=e();console.log("[Authorization] Message hash:",r),console.log("[Authorization] Recovering signer from signature..."),console.log(" signature:",t.signature);const i=await this.recoverSigner(r,t.signature);console.log(" recoveredAddress:",i),console.log("[Authorization] Checking authorization:"),console.log(" recoveredAddress:",i),console.log(" recoveredAddress (lowercase):",i.toLowerCase());const n=o(i);return console.log(" match:",n?"\u2705 YES":"\u274c NO"),n||console.error("[Authorization] Not authorized:",`\n Recovered: ${i}`),n}static async verifyAmountPositionActionAuthorization(t,e){return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action)),o="string"==typeof t.amount?BigInt(t.amount):t.amount;console.log("[Authorization] Building message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" amount (string):",t.amount),console.log(" amount (BigInt):",o.toString()),console.log(" action:",t.action),console.log(" actionHash:",e);const r=[t.positionId,t.timestamp,t.chainId,o.toString(),e],i=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","bytes32"],r);return console.log(" messageHash (solidityKeccak256):",i),i}),(t=>t.toLowerCase()===e.toLowerCase()))}static async verifyMintAuthorization(t,e){return"mint-ucd"!==t.action?(console.error(`[Authorization] Invalid action for mint: expected "mint-ucd", got "${t.action}"`),!1):this.verifyAmountPositionActionAuthorization(t,e)}static async verifyWithdrawAuthorization(t,e){if("withdraw-btc"!==t.action)return console.error(`[Authorization] Invalid action for withdrawal: expected "withdraw-btc", got "${t.action}"`),!1;return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action)),o="string"==typeof t.amount?BigInt(t.amount):t.amount;console.log("[Authorization] Building withdrawal message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" amount (string):",t.amount),console.log(" amount (BigInt):",o.toString()),console.log(" destinationAddress:",t.destinationAddress),console.log(" action:",t.action),console.log(" actionHash:",e);const r=[t.positionId,t.timestamp,t.chainId,o.toString(),t.destinationAddress,e],i=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","string","bytes32"],r);return console.log(" messageHash (solidityKeccak256):",i),i}),(t=>t.toLowerCase()===e.toLowerCase()))}static verifyTermPositionActionAuthorizationStructure(t){if(!t||"object"!=typeof t)throw new Error('Missing or invalid "auth" parameter - must be an object');if(!t.positionId)throw new Error("auth.positionId is required");if("number"!=typeof t.chainId)throw new Error("auth.chainId must be a number");if("number"!=typeof t.timestamp)throw new Error("auth.timestamp must be a number");if(void 0===t.newTerm||null===t.newTerm)throw new Error("auth.newTerm is required");if("number"!=typeof t.newTerm)throw new Error("auth.newTerm must be a number");if(!t.action)throw new Error("auth.action is required");if(!t.signature)throw new Error("auth.signature is required");if(!t.mode)throw new Error("auth.mode is required");if("dev"!==t.mode&&"prod"!==t.mode)throw new Error(`auth.mode must be "dev" or "prod", got: ${t.mode}`);return t}static async verifyTermPositionActionAuthorization(t,e){return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action));console.log("[Authorization] Building term message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" newTerm:",t.newTerm),console.log(" action:",t.action),console.log(" actionHash:",e);const o=[t.positionId,t.timestamp,t.chainId,t.newTerm,e],r=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","bytes32"],o);return console.log(" messageHash (solidityKeccak256):",r),r}),(t=>t.toLowerCase()===e.toLowerCase()))}static async verifyExtendAuthorization(t,e){return"extend-position"!==t.action?(console.error(`[Authorization] Invalid action for extension: expected "extend-position", got "${t.action}"`),!1):this.verifyTermPositionActionAuthorization(t,e)}static async verifyBtcExecutionAuthorization(t,e){if("execute-btc-withdrawal"!==t.action)return console.error(`[Authorization] Invalid action for BTC execution: expected "execute-btc-withdrawal", got "${t.action}"`),!1;return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action));console.log("[Authorization] Building BTC execution message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" utxoIdentifier:",t.utxoIdentifier),console.log(" networkFee:",t.networkFee),console.log(" action:",t.action),console.log(" actionHash:",e);const o=[t.positionId,t.timestamp,t.chainId,t.utxoIdentifier,t.networkFee,e],r=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","string","uint256","bytes32"],o);return console.log(" messageHash (solidityKeccak256):",r),r}),(t=>t.toLowerCase()===e.toLowerCase()))}},g=class{stepStart(t,e){}stepEnd(t,e){}log(t,e){}warn(t,e){}error(t,e){}getElapsed(){return 0}getRemaining(){return 3e4}summary(){}getExecutionId(){return""}isDebugEnabled(){return!1}},w=class{constructor(t){this.stepStartTimes=new Map,this.stepDurations=new Map,this.TIMEOUT_MS=3e4,this.actionName=t,this.executionStartTime=Date.now();const e=Date.now(),o=Math.random().toString(36).substring(2,9);this.executionId=`exec_${e}_${o}`,this.logHeader()}logHeader(){console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution started`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`);void 0!==globalThis.litNodeContext?(console.log(`[${this.actionName}] Context: LIT Node`),console.log(`[${this.actionName}] Node: ${globalThis.litNodeContext?.nodeAddress||"unknown"}`)):console.log(`[${this.actionName}] Context: Browser/Test`),console.log(`[${this.actionName}] ========================================`)}stepStart(t,e){this.stepStartTimes.set(t,Date.now());const o=Date.now()-this.executionStartTime,r=this.TIMEOUT_MS-o;console.log(`[Step ${t}] ${e}...`),console.log(`[Step ${t}] Elapsed: ${o}ms | Remaining: ${r}ms`)}stepEnd(t,e=5e3){const o=this.stepStartTimes.get(t);if(!o)return console.warn(`[Step ${t}] \u26a0\ufe0f stepEnd called without matching stepStart`),void 0;const r=Date.now()-o;this.stepDurations.set(t,r),console.log(`[Step ${t}] Duration: ${r}ms`),r>e&&console.warn(`\u26a0\ufe0f [Step ${t}] Took ${r}ms (> ${e}ms threshold)`);const i=Date.now()-this.executionStartTime,n=this.TIMEOUT_MS-i;n<5e3&&console.warn(`\u26a0\ufe0f [Step ${t}] Time remaining: ${n}ms (approaching timeout)`)}log(t,e){console.log(`[Step ${t}] ${e}`)}warn(t,e){console.warn(`\u26a0\ufe0f [Step ${t}] ${e}`)}error(t,e){console.error(`\u274c [Step ${t}] ${e}`)}getElapsed(){return Date.now()-this.executionStartTime}getRemaining(){return this.TIMEOUT_MS-this.getElapsed()}summary(){const t=Date.now()-this.executionStartTime,e=this.TIMEOUT_MS-t;if(console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution Summary`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`),console.log(`[${this.actionName}] Total time: ${(t/1e3).toFixed(2)}s`),console.log(`[${this.actionName}] Time remaining: ${e}ms`),this.stepDurations.size>0){console.log(`[${this.actionName}] Step breakdown:`);for(const[e,o]of this.stepDurations.entries()){const r=(o/t*100).toFixed(1);console.log(`[${this.actionName}] Step ${e}: ${o}ms (${r}%)`)}}t>2e4&&console.warn(`\u26a0\ufe0f [${this.actionName}] Slow execution: ${(t/1e3).toFixed(2)}s (${e}ms remaining)`),e<5e3&&console.warn(`\u26a0\ufe0f [${this.actionName}] CRITICAL: Only ${e}ms remaining before timeout!`),console.log(`[${this.actionName}] ========================================`)}getExecutionId(){return this.executionId}isDebugEnabled(){return!0}},f=class{static create(t="LIT Action",e){return e??globalThis.debugAction??!1?new w(t):new g}},y=class{constructor(t){this.config=t,this.timeout=t.timeout||15e3,this.logger=f.create("BitcoinDataProvider")}async getCurrentBlockHeight(){if(this.logger.stepStart("0","getCurrentBlockHeight"),this.config.rpcHelper){const t=await this.config.rpcHelper.getBlockCount();return this.logger.stepEnd("0"),t}const t=new AbortController,e=setTimeout((()=>t.abort()),this.timeout);try{const o=await fetch(`${this.config.providerUrl}/blocks/tip/height`,{signal:t.signal,headers:{Accept:"text/plain","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(e),!o.ok)throw new Error(`Failed to fetch block height: ${o.status} ${o.statusText}`);const r=await o.text(),i=parseInt(r.trim(),10);if(isNaN(i))throw new Error(`Invalid block height response: ${r}`);return this.logger.stepEnd("0"),i}catch(t){if(clearTimeout(e),this.logger.stepEnd("0"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOs(t){this.logger.log("0",`Original address: "${t}"`);const e=this.stripNetworkPrefix(t);if(this.logger.log("0",`Cleaned address: "${e}"`),this.config.rpcHelper)return await this.fetchUTXOsFromRPC(e);try{return await this.fetchUTXOsFromProvider(this.config.providerUrl,e)}catch(t){if(this.logger.warn("0",`Primary provider failed: ${t.message}`),this.config.fallbackProviders&&this.config.fallbackProviders.length>0){const t=[...this.config.fallbackProviders].sort(((t,e)=>t.priority-e.priority));for(const o of t)try{return this.logger.log("0",`Trying fallback: ${o.name} (${o.url})`),await this.fetchUTXOsFromProvider(o.url,e)}catch(t){this.logger.warn("0",`Fallback ${o.name} failed: ${t.message}`);continue}}throw new Error(`Failed to fetch UTXOs: ${t.message}`)}}stripNetworkPrefix(t){return t.replace(/^(REGTEST_|TESTNET_|MAINNET_)/,"")}async fetchUTXOsFromRPC(t){if(!this.config.rpcHelper)throw new Error("RPC helper not configured");const e=this.config.rpcWallet||"";return(await this.config.rpcHelper.listUnspent(e,t)).map((t=>({txid:t.txid,vout:t.vout,satoshis:BigInt(Math.round(1e8*t.amount)),confirmations:t.confirmations})))}parseBlockstreamUTXOs(t,e){if(!Array.isArray(t))throw new Error("Invalid Blockstream response format: expected an array");return t.map((t=>{let o=0;return t.status&&"object"==typeof t.status?t.status.confirmed&&(o=void 0!==t.status.block_height&&t.status.block_height>0?e-t.status.block_height+1:1):void 0!==t.confirmations&&(o=t.confirmations),{txid:t.txid,vout:void 0!==t.vout?t.vout:t.n,satoshis:BigInt(void 0!==t.value?t.value:t.satoshis),confirmations:o}}))}async fetchUTXOsFromProvider(t,e){this.logger.stepStart("1","fetchUTXOsFromProvider");const o=`${t}/address/${e}/utxos`;this.logger.log("1",`Fetching UTXOs from ${o}`);const r=await this.getCurrentBlockHeight();this.logger.log("1",`Current block height: ${r}`);const i=new AbortController,n=setTimeout((()=>i.abort()),this.timeout);try{const t=await fetch(o,{signal:i.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(n),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();this.logger.log("1",`Raw API Response: ${JSON.stringify(e,null,2)}`);const a=this.parseBlockstreamUTXOs(e,r);return this.logger.stepEnd("1"),a}catch(t){if(clearTimeout(n),this.logger.stepEnd("1"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOSet(t,e){let o=[],r=null;for(let e=1;e<=3;e++)try{this.logger.log("2",`UTXO query attempt ${e}/3...`),o=await this.getUTXOs(t),this.logger.log("2",`UTXO query succeeded on attempt ${e}`),r=null;break}catch(t){r=t,this.logger.error("2",`UTXO query failed on attempt ${e}: ${t.message}`),e<3&&(this.logger.log("2","Waiting 500ms before retry..."),await new Promise((t=>setTimeout(t,500))))}if(r)throw new Error(`Failed to query UTXOs after 3 attempts: ${r.message}`);const i=o.reduce(((t,e)=>t+e.satoshis),0n),n=o.filter((t=>t.confirmations>=e)).reduce(((t,e)=>t+e.satoshis),0n),a=i-n;return{utxos:o,totalBalance:i,totalUTXOs:o.length,confirmedBalance:n,unconfirmedBalance:a}}async getBalance(t){const e=this.stripNetworkPrefix(t),o=`${this.config.providerUrl}/address/${e}`;this.logger.log("3",`Fetching balance from ${o}`);const r=new AbortController,i=setTimeout((()=>r.abort()),this.timeout);try{const t=await fetch(o,{signal:r.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(i),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();if(!e.chain_stats)throw new Error("Invalid response: missing chain_stats");const n=e.chain_stats.funded_txo_sum,a=e.chain_stats.spent_txo_sum;if(void 0===n||void 0===a)throw new Error(`Invalid response: missing required fields. funded_txo_sum: ${n}, spent_txo_sum: ${a}`);const s=BigInt(n-a);return this.logger.log("3",`Balance: ${s.toString()} sats`),this.logger.log("3",` - Funded: ${n} sats, Spent: ${a} sats`),s}catch(t){if(clearTimeout(i),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);const o=t.message||"Unknown error";throw this.logger.log("3",`Balance fetch failed: ${o}`),new Error(`Failed to fetch Bitcoin balance for address ${e}: ${o}`)}}async getTransaction(t){if(this.config.rpcHelper)try{const e=this.config.rpcWallet||"",o=await this.config.rpcHelper.getTransaction(e,t);return{txid:o.txid,confirmations:o.confirmations||0}}catch(t){if(t.message?.includes("Invalid or non-wallet transaction")||-5===t.code)return null;throw t}const e=new AbortController,o=setTimeout((()=>e.abort()),this.timeout);try{const r=await fetch(`${this.config.providerUrl}/tx/${t}`,{signal:e.signal});if(clearTimeout(o),!r.ok){if(404===r.status)return null;throw new Error(`Bitcoin provider error: ${r.status} ${r.statusText}`)}const i=await r.json();return i.status?{txid:i.txid,confirmations:i.status.confirmed&&i.status.block_height?1:0}:null}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);if(t.message?.includes("404"))return null;throw t}}};function b(t,e){if(0===t)return{termDurationDays:0,termLengthDays:30*e,isExpired:!1,daysUntilExpiry:30*e,daysIntoGracePeriod:0};const o=30*e,r=Math.floor(Date.now()/1e3)-t,i=Math.floor(r/86400);return{termDurationDays:i,termLengthDays:o,isExpired:i>o,daysUntilExpiry:Math.max(0,o-i),daysIntoGracePeriod:Math.max(0,i-o)}}function T(t,e){if(!t)return 13e3;const o=Math.min(e,30);return 11e3+9e3*(o*o)/900}function v(t,o,r){const i=t*o/10000000000000000n;if(0n===r)return{collateralValueUsd:i,collateralRatioBps:Number.MAX_SAFE_INTEGER};return{collateralValueUsd:i,collateralRatioBps:Number(i*e*10000n/r)}}function A(t,e){if(t<0n)throw new Error("Collateral value cannot be negative");if(e<=0n)throw new Error("Debt must be positive to calculate ratio");const o=Number(10000n*(10000000000n*t)/e);if(o<0)throw new Error(`Collateral ratio out of bounds: ${o} bps (${o/100}%)`);return!isFinite(o)||o>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:o}function E(t){return t/100}var S=class{constructor(t,e,o){this.mode=o,e&&Array.isArray(e)?this.sources=e:t&&Array.isArray(t)?this.sources=this.buildSources(t):this.sources=this.buildSources(),this.sources.sort(((t,e)=>t.priority-e.priority)),this.validateProviderCount()}validateProviderCount(){if("prod"===this.mode&&this.sources.length<3)throw new Error(`Production mode requires at least 3 price providers for consensus. Currently configured: ${this.sources.length} provider(s). Supported providers: CoinGecko, Binance, Coinbase, CryptoCompare (with API key)`);if(0===this.sources.length)throw new Error("No price providers configured. At least one provider is required.")}buildSources(t){const e=[];if(!t||0===t.length)return this.getDefaultSources();let o=1;for(const r of t){switch(r.name.toLowerCase()){case"coingecko":e.push(this.createCoinGeckoSource(r.apiKey,o++));break;case"binance":e.push(this.createBinanceSource(r.apiKey,r.apiSecret,o++));break;case"coinbase":e.push(this.createCoinbaseSource(r.apiKey,o++));break;case"cryptocompare":if(!r.apiKey)throw new Error("CryptoCompare requires an API key. Please provide apiKey in priceProviders config.");e.push(this.createCryptoCompareSource(r.apiKey,o++));break;default:console.warn(`[Price Oracle] Unknown provider: ${r.name} - skipping`)}}return e}createCryptoCompareSource(t,e){return{name:"CryptoCompare",fetchPrice:async()=>{const e=new URL("https://min-api.cryptocompare.com/data/price");e.searchParams.set("fsym","BTC"),e.searchParams.set("tsyms","USDT"),e.searchParams.set("api_key",t);const o=await(async t=>{const e=new AbortController,o=setTimeout((()=>e.abort()),5e3);try{const r=await fetch(t,{signal:e.signal,headers:{Accept:"application/json"}});if(clearTimeout(o),!r.ok)throw new Error(`HTTP ${r.status} ${r.statusText}`);return await r.json()}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error("Request timeout after 5000ms");throw t}})(e.toString()),r=Number(o?.USDT);if(!Number.isFinite(r)||r<=0)throw new Error("Invalid CryptoCompare price payload");const i=(await Lit.Actions.broadcastAndCollect({name:"cryptoComparePrice",value:r.toString()})).map((t=>parseFloat(t)));return i.sort(((t,e)=>t-e)),i[Math.floor(i.length/2)]},priority:e}}createFetchJson(){return async(t,e)=>{const o=new AbortController,r=setTimeout((()=>o.abort()),5e3);try{const r=await fetch(t,{headers:{Accept:"application/json",...e||{}},signal:o.signal});if(!r.ok)throw new Error(`HTTP ${r.status} ${r.statusText}`);return await r.json()}finally{clearTimeout(r)}}}getDefaultSources(){return[this.createCoinGeckoSource(void 0,1),this.createBinanceSource(void 0,void 0,2),this.createCoinbaseSource(void 0,3)]}createCoinGeckoSource(t,e=1){const o=this.createFetchJson();return{name:"CoinGecko",fetchPrice:async()=>{const t=new URL("https://api.coingecko.com/api/v3/simple/price");t.searchParams.set("ids","bitcoin"),t.searchParams.set("vs_currencies","usd");const e=await o(t.toString()),r=Number(e?.bitcoin?.usd);if(!Number.isFinite(r)||r<=0)throw new Error("Invalid CoinGecko price payload");const i=(await Lit.Actions.broadcastAndCollect({name:"coinGeckoPrice",value:r.toString()})).map((t=>parseFloat(t)));return i.sort(((t,e)=>t-e)),i[Math.floor(i.length/2)]},priority:e}}createBinanceSource(t,e,o=2){const r=this.createFetchJson();return{name:"Binance",fetchPrice:async()=>{const e=new URL("https://api.binance.com/api/v3/ticker/price");e.searchParams.set("symbol","BTCUSDT");const o=t?{"X-MBX-APIKEY":t}:void 0,i=await r(e.toString(),o),n=Number(i?.price);if(!Number.isFinite(n)||n<=0)throw new Error("Invalid Binance price payload");const a=(await Lit.Actions.broadcastAndCollect({name:"binancePrice",value:n.toString()})).map((t=>parseFloat(t)));return a.sort(((t,e)=>t-e)),a[Math.floor(a.length/2)]},priority:o}}createCoinbaseSource(t,e=3){const o=this.createFetchJson();return{name:"Coinbase",fetchPrice:async()=>{const e=t?{"CB-ACCESS-KEY":t}:void 0,r=await o("https://api.coinbase.com/v2/prices/BTC-USD/spot",e),i=Number(r?.data?.amount);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid Coinbase price payload");const n=(await Lit.Actions.broadcastAndCollect({name:"coinbasePrice",value:i.toString()})).map((t=>parseFloat(t)));return n.sort(((t,e)=>t-e)),n[Math.floor(n.length/2)]},priority:e}}async getBTCPrice(){const t=Date.now();console.log("[Price Oracle] Fetching BTC price from external sources (parallel)..."),console.log(`[Price Oracle] Start time: ${t}`);const e=new Promise(((e,o)=>{setTimeout((()=>{const e=Date.now()-t;console.error(`[Price Oracle] \u274c GLOBAL TIMEOUT after ${e}ms`),o(new Error("Price oracle global timeout after 15000ms"))}),15e3)})),o=this.sources.map((async t=>{const e=Date.now();try{console.log(`[Price Oracle] [${e}] Querying ${t.name}...`);const o=await t.fetchPrice(),r=Date.now()-e;return console.log(`[Price Oracle] \u2705 [+${r}ms] ${t.name}: $${o.toLocaleString()}`),{source:t.name,priceUSD:o}}catch(o){const r=Date.now()-e;throw console.warn(`[Price Oracle] \u26a0\ufe0f [+${r}ms] ${t.name} failed: ${o.message}`),o}}));try{console.log("[Price Oracle] Waiting for first success (race pattern)...");const r=await Promise.race([Promise.race(o.map((t=>t.catch((t=>({error:t})))))),e]);if("error"in r){console.log("[Price Oracle] First result was error, waiting for any success...");const e=Date.now(),r=new Promise(((t,o)=>{setTimeout((()=>{const t=Date.now()-e;console.error(`[Price Oracle] \u274c FALLBACK TIMEOUT after ${t}ms`),o(new Error("Price oracle fallback timeout after 10000ms"))}),1e4)})),i=await Promise.race([Promise.allSettled(o),r]),n=Date.now()-e;console.log(`[Price Oracle] Fallback completed in ${n}ms`);const a=i.find((t=>"fulfilled"===t.status));if(!a){const e=Date.now()-t;throw console.error(`[Price Oracle] All sources failed after ${e}ms`),new Error("All price sources failed")}const{source:s,priceUSD:c}=a.value,l=Date.now()-t;console.log(`[Price Oracle] Using ${s} (first successful after failures) - total time: ${l}ms`);const u=Math.round(100*c),d=1000000n*BigInt(u);return console.log(`[Price Oracle] Price with 8 decimals: ${d}`),d}const{source:i,priceUSD:n}=r,a=Date.now()-t;console.log(`[Price Oracle] Using ${i} (fastest response) - total time: ${a}ms`);const s=Math.round(100*n),c=1000000n*BigInt(s);return console.log(`[Price Oracle] Price with 8 decimals: ${c}`),c}catch(e){const o=Date.now()-t;throw console.error(`[Price Oracle] Failed after ${o}ms: ${e.message}`),new Error(`All price sources failed: ${e.message}`)}}async getBTCPriceConsensus(){console.log("[Price Oracle] Fetching BTC price with consensus...");const t=(await Promise.allSettled(this.sources.map((async t=>{const e=await t.fetchPrice();return{source:t.name,price:e}})))).filter((t=>"fulfilled"===t.status)).map((t=>t.value));if(0===t.length)throw new Error("No price sources returned data");console.log(`[Price Oracle] Got prices from ${t.length}/${this.sources.length} sources:`),t.forEach((t=>{console.log(` ${t.source}: $${t.price.toLocaleString()}`)}));const e=t.map((t=>t.price));e.sort(((t,e)=>t-e));const o=e[Math.floor(e.length/2)],r=e[0],i=e[e.length-1]/r,n=t.filter((t=>Math.abs(t.price-o)/o<=.02));if(i>1.05&&n.length===t.length)throw new Error(`Price consensus failed: sources too dispersed (${(100*(i-1)).toFixed(1)}% spread)`);if(n.length<t.length){const e=t.filter((t=>!n.find((e=>e.source===t.source))));if(console.log(`[Price Oracle] \u26a0\ufe0f Detected ${e.length} outlier(s):`),e.forEach((t=>{const e=Math.abs(t.price-o)/o;console.log(` ${t.source}: $${t.price.toLocaleString()} (${(100*e).toFixed(1)}% deviation)`)})),n.length<2)throw new Error("Price consensus failed: insufficient valid sources after outlier removal");console.log(`[Price Oracle] \u2705 Outliers filtered, continuing with ${n.length} valid sources`);const r=n.map((t=>t.price));r.sort(((t,e)=>t-e));const i=r[Math.floor(r.length/2)];console.log(`[Price Oracle] \u2705 Consensus price (median): $${i.toLocaleString()}`);const a=Math.round(100*i);return 1000000n*BigInt(a)}console.log(`[Price Oracle] \u2705 Consensus price (median): $${o.toLocaleString()}`);const a=Math.round(100*o);return 1000000n*BigInt(a)}},$=class{constructor(t){this.config=t,this.bitcoinProvider=t.bitcoinProvider}async calculateBalance(t,e){const o=await this.bitcoinProvider.getBalance(e),r=await this.getAuthorizedSpendsFromContract(t),i=r.reduce(((t,e)=>t+e.satoshis),0n),n=o>i?o-i:0n;return{totalUTXOs:[],totalBalance:o,authorizedUTXOs:r,authorizedBalance:i,authorizedSpendsHash:this.computeAuthorizedSpendsHash(t,r),availableUTXOs:[],availableBalance:n,vaultAddress:e,positionId:t,timestamp:Date.now()}}async calculateTrustedBalance(t,e,o){if(o)return await this.calculateBalance(t,e);const r=this.config.minConfirmations||6,i=await this.bitcoinProvider.getUTXOSet(e,r),n=await this.getAuthorizedSpendsFromContract(t);for(const t of n){if(i.utxos.some((e=>e.txid===t.txid&&e.vout===t.vout)))throw new Error(`Authorized UTXO ${t.txid}:${t.vout} not yet spent. Transaction was authorized in smart contract but not signed and broadcasted to Bitcoin network. Complete the transaction by signing and broadcasting it.`)}const a=n.reduce(((t,e)=>t+e.satoshis),0n),s=i.utxos.filter((t=>!this.isUTXOAuthorized(t,n))),c=s.reduce(((t,e)=>t+e.satoshis),0n),l=this.computeAuthorizedSpendsHash(t,n);return{totalUTXOs:i.utxos,totalBalance:i.totalBalance,authorizedUTXOs:n,authorizedBalance:a,authorizedSpendsHash:l,availableUTXOs:s,availableBalance:c,vaultAddress:e,positionId:t,timestamp:Date.now()}}async getTrustedBalance(t,e){return(await this.calculateTrustedBalance(t,e)).availableBalance}async getAvailableUTXOs(t,e){return(await this.calculateTrustedBalance(t,e)).availableUTXOs}async isUTXOAvailable(t,e,o,r){return(await this.getAvailableUTXOs(t,e)).some((t=>t.txid===o&&t.vout===r))}isUTXOAuthorized(t,e){return e.some((e=>e.txid===t.txid&&e.vout===t.vout))}async getAuthorizedSpendsFromContract(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let o;o=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const r=new ethers.providers.StaticJsonRpcProvider(o,{name:"any",chainId:this.config.chainId}),i=new ethers.Contract(this.config.contractAddress,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getAuthorizedSpends",outputs:[{components:[{internalType:"string",name:"txid",type:"string"},{internalType:"uint32",name:"vout",type:"uint32"},{internalType:"uint256",name:"satoshis",type:"uint256"},{internalType:"string",name:"targetAddress",type:"string"},{internalType:"uint256",name:"targetAmount",type:"uint256"},{internalType:"uint256",name:"authorizedAt",type:"uint256"}],internalType:"struct LoanOperationsManager.AuthorizedSpend[]",name:"",type:"tuple[]"}],stateMutability:"view",type:"function"}],r);return(await i.getAuthorizedSpends(e)).map((e=>({txid:e.txid,vout:Number(e.vout),satoshis:BigInt(e.satoshis.toString()),positionId:t,targetAddress:e.targetAddress,targetAmount:BigInt(e.targetAmount.toString()),timestamp:Number(e.authorizedAt)})))}computeAuthorizedSpendsHash(t,e){const o=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;if(0===e.length)return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[o,[]]));const r=e.map((t=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.txid)),o=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.targetAddress));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","uint32","uint256","bytes32","uint256","uint256"],[e,t.vout,t.satoshis.toString(),o,t.targetAmount.toString(),t.timestamp]))}));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[o,r]))}};var I=class{constructor(t){this.termManagerAddress=t.termManagerAddress,this.loanOpsManagerAddress=t.loanOpsManagerAddress,this.chain=t.chain,this.chainId=t.chainId,this.rpcUrl=t.rpcUrl}async getLiquidationThreshold(){try{let t;t=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const e=new ethers.providers.StaticJsonRpcProvider(t,{name:"any",chainId:this.chainId}),o=[{inputs:[],name:"liquidationThreshold",outputs:[{internalType:"uint256",name:"",type:"uint256"}],stateMutability:"view",type:"function"}],r=new ethers.Contract(this.loanOpsManagerAddress,o,e),i=await r.liquidationThreshold();return Number(i.toString())}catch(t){throw console.error("[ProtocolParameters] Error fetching liquidation threshold:",t.message),new Error(`Failed to fetch liquidation threshold: ${t.message}`)}}async getTermFees(t){try{let e;e=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const o=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),r=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint88",name:"originationFee",type:"uint88"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],i=new ethers.Contract(this.termManagerAddress,r,o),n=await i.getTermFees(t);return{originationFeeBps:Number(n.originationFee?.toString?.()??n[0]?.toString?.()??n[0]),extensionFeeBps:Number(n.extensionFee?.toString?.()??n[1]?.toString?.()??n[1])}}catch(t){throw console.error("[ProtocolParameters] Error fetching term fees:",t.message),new Error(`Failed to fetch term fees: ${t.message}`)}}async getAuthorizedSpendsHash(t){try{const e=this.rpcUrl||await Lit.Actions.getRpcUrl({chain:this.chain}),o=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),r=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`,i=new ethers.Contract(this.loanOpsManagerAddress,[{inputs:[{name:"positionId",type:"bytes32"}],name:"getAuthorizedSpendsHash",outputs:[{name:"",type:"bytes32"}],stateMutability:"view",type:"function"}],o);return await i.getAuthorizedSpendsHash(r)}catch(t){throw console.error("[ProtocolParameters] Error fetching authorized spends hash:",t.message),new Error(`Failed to fetch authorized spends hash: ${t.message}`)}}};function P(t){return new I(t)}var x=class{constructor(t){this.config=t}async getVaultSnapshot(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const o=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress),r=await this.config.priceOracle.getBTCPrice(),i=P({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[n,a]=await Promise.all([i.getLiquidationThreshold(),i.getTermFees(e.selectedTerm)]),s=b(e.termStartTimestamp,e.selectedTerm),c=T(s.isExpired,s.daysIntoGracePeriod),l=v(o.availableBalance,r,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:o.totalBalance,totalUTXOs:o.totalUTXOs,authorizedSpendsSats:o.authorizedBalance,authorizedSpendsHash:o.authorizedSpendsHash,availableBTCSats:o.availableBalance,availableUTXOs:o.availableUTXOs,btcPriceUsd:r,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:s.termDurationDays,termLengthDays:s.termLengthDays,isExpired:s.isExpired,daysUntilExpiry:s.daysUntilExpiry,daysIntoGracePeriod:s.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:n,originationFeeBps:a.originationFeeBps,extensionFeeBps:a.extensionFeeBps,timestamp:Date.now()}}async getVaultSnapshotFast(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const o=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress,!0),r=await this.config.priceOracle.getBTCPrice(),i=P({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[n,a]=await Promise.all([i.getLiquidationThreshold(),i.getTermFees(e.selectedTerm)]),s=b(e.termStartTimestamp,e.selectedTerm),c=T(s.isExpired,s.daysIntoGracePeriod),l=v(o.availableBalance,r,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:o.totalBalance,totalUTXOs:o.totalUTXOs,authorizedSpendsSats:o.authorizedBalance,authorizedSpendsHash:o.authorizedSpendsHash,availableBTCSats:o.availableBalance,availableUTXOs:o.availableUTXOs,btcPriceUsd:r,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:s.termDurationDays,termLengthDays:s.termLengthDays,isExpired:s.isExpired,daysUntilExpiry:s.daysUntilExpiry,daysIntoGracePeriod:s.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:n,originationFeeBps:a.originationFeeBps,extensionFeeBps:a.extensionFeeBps,timestamp:Date.now()}}async queryPositionState(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let o;o=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const r=new ethers.providers.StaticJsonRpcProvider(o,{name:"any",chainId:this.config.chainId}),i=new ethers.Contract(this.config.contractAddress,[{inputs:[],name:"core",outputs:[{internalType:"address",name:"",type:"address"}],stateMutability:"view",type:"function"}],r),n=await i.core(),a=new ethers.Contract(n,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getPositionDetails",outputs:[{components:[{internalType:"bytes32",name:"positionId",type:"bytes32"},{internalType:"bytes32",name:"pkpId",type:"bytes32"},{internalType:"uint256",name:"ucdDebt",type:"uint256"},{internalType:"string",name:"vaultAddress",type:"string"},{internalType:"address",name:"borrower",type:"address"},{internalType:"uint40",name:"createdAt",type:"uint40"},{internalType:"uint40",name:"lastUpdated",type:"uint40"},{internalType:"uint16",name:"selectedTerm",type:"uint16"},{internalType:"uint40",name:"expiryAt",type:"uint40"},{internalType:"enum LoanStatusLib.LoanStatus",name:"status",type:"uint8"}],internalType:"struct IPositionManagerCore.Position",name:"",type:"tuple"}],stateMutability:"view",type:"function"}],r),s=await a.getPositionDetails(e),l=Number(s.status);if(l<0||l>7)throw console.error("[VaultSnapshot] INVALID STATUS - ABI decoding error detected"),console.error(" Position ID:",e),console.error(" Contract:",this.config.contractAddress),console.error(" Status decoded:",l,"(expected 0-7)"),console.error(" Full position:",JSON.stringify(s,null,2)),new Error(`Invalid position status decoded from contract: ${l} (expected 0-7). This indicates an ABI decoding issue. Position: ${e}`);const u=Number(s.expiryAt),d=Number(s.selectedTerm),h=(m=d,0===(p=u)?0:p-30*m*86400);var p,m;return{positionId:s.positionId,pkpId:s.pkpId,borrower:s.borrower,vaultAddress:this.parseVaultAddress(s.vaultAddress,c(this.config.chain)),ucdDebt:BigInt(s.ucdDebt.toString()),termStartTimestamp:h,selectedTerm:d,status:l,expiryAt:u}}parseVaultAddress(t,e){try{const o=JSON.parse(t);if("object"==typeof o&&null!==o){if("sepolia"===e.toLowerCase())return o.regtest||o.testnet||o.mainnet;return"testnet"===l(e)&&(o.testnet||o.regtest)||o.mainnet}}catch(t){}return t}async isLiquidatable(t){return(await this.getVaultSnapshot(t)).isLiquidatable}async getCollateralRatio(t){return(await this.getVaultSnapshot(t)).collateralRatioBps}async getAvailableBalance(t){return(await this.getVaultSnapshot(t)).availableBTCSats}async hasSufficientCollateral(t,o,r){const i=await this.getVaultSnapshot(t);return function(t,o,r,i){const n=o+r;return 0n===n||Number(t*e*10000n/n)>=i}(i.collateralValueUsd,i.ucdDebt,o,r)}};var B=1000000000000n,C=100000000000000n,U=[{inputs:[{internalType:"uint256",name:"term",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint256",name:"originationFee",type:"uint256"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],k=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"isValidTerm",outputs:[{internalType:"bool",name:"",type:"bool"}],stateMutability:"view",type:"function"}],D=[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"isExtensionPending",outputs:[{internalType:"bool",name:"",type:"bool"}],stateMutability:"view",type:"function"}];(async()=>{let e="0";try{console.log("[Extend Position Validator] Starting..."),console.log("[Extend Position Validator] DEBUG: globalThis.priceProviders:",globalThis.priceProviders),console.log("[Extend Position Validator] DEBUG: priceProviders type:",typeof globalThis.priceProviders),globalThis.priceProviders&&(console.log("[Extend Position Validator] DEBUG: priceProviders isArray:",Array.isArray(globalThis.priceProviders)),console.log("[Extend Position Validator] DEBUG: priceProviders length:",globalThis.priceProviders.length),globalThis.priceProviders.length>0&&console.log("[Extend Position Validator] DEBUG: First provider:",globalThis.priceProviders[0])),e="0";const r=globalThis.chain,n=globalThis.bitcoinProviderUrl,a=globalThis.selectedTerm;if(!r)throw new Error('Missing required parameter: "chain"');if(!n)throw new Error('Missing required parameter: "bitcoinProviderUrl"');if(!a||"number"!=typeof a)throw new Error('Missing required parameter: "selectedTerm"');const d=m.verifyTermPositionActionAuthorizationStructure(globalThis.auth),p=d.mode;let g,w,f,b,T,v,I;if("prod"===p?(w=c(r),f=l(w),g=function(t){for(const e of Object.values(i)){const o=s[e].find((e=>e.url===t));if(o)return o}const e=Object.entries(s).flatMap((([t,e])=>e.map((e=>`${t}: ${e.name} (${e.url})`)))).join(", ");throw new Error(`Bitcoin provider not approved. Provided URL: ${t}. Approved providers: ${e}`)}(n)):(w=r,f="sepolia"===r?"testnet":"regtest",g={name:"Custom Provider",url:n,minConfirmations:1,network:f}),"dev"!==p){u(w);throw new Error("Production mode contract addresses not yet configured. Use dev mode for testing.")}if(!globalThis.contractAddresses||"object"!=typeof globalThis.contractAddresses)throw new Error('Dev mode requires "contractAddresses" in globalThis');if(b=globalThis.contractAddresses.PositionManager,T=globalThis.contractAddresses.LoanOperationsManagerModule,v=globalThis.contractAddresses.TermManagerModule,I=globalThis.contractAddresses.UCDController,!(b&&T&&v&&I))throw new Error("Missing required contract addresses");const P=globalThis.customRpcUrl,M="prod"===p?u(w):d.chainId,O=P?new ethers.providers.JsonRpcProvider(P):new ethers.providers.JsonRpcProvider("sepolia"===r?"https://rpc.sepolia.org":"https://eth.llamarpc.com"),F=new y({providerUrl:g.url,network:g.network}),N=new S(globalThis.priceProviders,void 0),z=(o={contractAddress:T,chain:r,chainId:M,rpcUrl:P,bitcoinProvider:F,minConfirmations:"prod"===p?g.minConfirmations:1},new $(o));e="1";const L=function(t){return new x(t)}({contractAddress:b,termManagerAddress:v,loanOpsManagerAddress:T,chain:r,chainId:M,rpcUrl:P,vaultBalance:z,priceOracle:N});console.log("[Extend Position Validator] Using fast balance path (skipUTXOValidation: true)");const R=await L.getVaultSnapshot(d.positionId,!0),q=await N.getBTCPrice(),X=BigInt(q.toString());if(X<B||X>C)throw new Error(`Bitcoin price ${X} is outside acceptable range (1000000000000 - 100000000000000)`);e="2";if(!await m.verifyExtendAuthorization(d,R.borrower))throw new Error("Caller not authorized");e="3";if(![2,3].includes(R.status))throw new Error(`Invalid position status for extension: ${function(t){switch(t){case 0:return"PENDING_DEPOSIT";case 1:return"PENDING_MINT";case 2:return"ACTIVE";case 3:return"EXPIRED";case 4:return"LIQUIDATABLE";case 5:return"LIQUIDATED";case 6:return"REPAID";case 7:return"CLOSED";default:return`UNKNOWN(${t})`}}(R.status)}. Must be ACTIVE or EXPIRED.`);const H=d.positionId.startsWith("0x")?d.positionId:`0x${d.positionId.padStart(64,"0")}`,_=new ethers.Contract(b,[...D],O),V=new ethers.Contract(v,[...k,...U],O),[G,j]=await Promise.all([_.isExtensionPending(H),V.isValidTerm(a)]);if(G)throw new Error("Extension already in progress for this position. Please wait for the current extension to complete.");if(!j)throw new Error(`Invalid extension term: ${a} months. This term is not configured in TermManager.`);if(R.isLiquidatable)throw new Error("Position is liquidatable - extension rejected. Add collateral first.");const K=R.selectedTerm,J=K+a,W=65535;if(J>W)throw new Error(`Extension would cause term overflow: ${K} + ${a} = ${J} months, exceeds max ${W} months`);const Q=Math.floor(Date.now()/1e3),Y=86400,Z=12===a?365*Y:a*30*Y,tt=R.expiryAt+Z,et=3===R.status;if(tt-Q>2*Z)throw new Error("Invalid requested expiry date: cannot extend beyond double term period from now");if(et&&tt<=Q)throw new Error("Invalid requested expiry date: extension must extend into the future for expired loans");e="4";const[,ot]=await V.getTermFees(a),rt=BigInt(ot.toString());e="5";const it=R.ucdDebt*rt/10000n,nt=R.ucdDebt+it;e="6";const at=function(e,o){if(e<0n)throw new Error("BTC amount cannot be negative");if(o<=0n)throw new Error("BTC price must be positive");return e*o/t}(R.availableBTCSats,X),st=A(at,nt);if(!function(t,e,o){if(o<0||o>1e6)throw new Error("Minimum ratio must be between 0 and 1000000 bps (0% to 10000%)");return A(t,e)>=o}(at,nt,R.liquidationThresholdBps))throw new Error(`Insufficient collateral after extension: ${E(st)}% < ${E(R.liquidationThresholdBps)}%`);e="7";const ct=10,lt=h()+ct,ut=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256","uint256","uint256"],[d.positionId,a,X.toString(),R.availableBTCSats.toString(),lt]),dt=ethers.utils.keccak256(ut),ht=globalThis.publicKey;if(!ht)throw new Error("Missing required parameter: publicKey");e="8";const pt=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(dt),publicKey:"0x"+ht,sigName:"extendPositionAuth"});Lit.Actions.setResponse({response:JSON.stringify({approved:!0,positionId:d.positionId,selectedTerm:a,btcPrice:X.toString(),availableBTCBalance:R.availableBTCSats.toString(),extensionFee:it.toString(),newTotalDebt:nt.toString(),newCollateralRatioBps:st.toString(),signature:pt,timestamp:d.timestamp,quantumTimestamp:lt,validatorPkp:ht})})}catch(t){console.error(`[Extend Position Validator] Failed at step ${e}:`,t.message),Lit.Actions.setResponse({response:JSON.stringify({approved:!1,reason:t.message||t.toString(),failedStep:e,positionId:globalThis.auth?.positionId,timestamp:Date.now()})})}var o})()})();
1
+ var _LIT_ACTION_=(()=>{var t=100000000n,e=1000000000000000000n,o=100;var r=(t=>(t.SEPOLIA="sepolia",t.ETHEREUM="ethereum",t.HARDHAT="hardhat",t))(r||{}),i=(t=>(t.TESTNET="testnet",t.MAINNET="mainnet",t.REGTEST="regtest",t))(i||{}),n={sepolia:"testnet",ethereum:"mainnet",hardhat:"regtest"},a={ethereum:1,sepolia:11155111,hardhat:31337},s={regtest:[{url:"https://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"regtest",minConfirmations:1,name:"Diamond Hands"},{url:"http://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"regtest",minConfirmations:1,name:"Diamond Hands"},{url:"http://127.0.0.1:18443",network:"regtest",minConfirmations:1,name:"Local Regtest"},{url:"https://313561dc30c5.ngrok-free.app",network:"regtest",minConfirmations:1,name:"Ngrok Bitcoin Tunnel"}],testnet:[{url:"https://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"testnet",minConfirmations:1,name:"Diamond Hands"},{url:"http://diamond-hands-btc-faucet-6b39a1072059.herokuapp.com/api/esplora",network:"testnet",minConfirmations:1,name:"Diamond Hands"}],mainnet:[{url:"https://blockstream.info/api",network:"mainnet",minConfirmations:6,name:"Blockstream Mainnet"},{url:"https://mempool.space/api",network:"mainnet",minConfirmations:6,name:"Mempool.space Mainnet"}]};function c(t){const e=t.toLowerCase().trim();if("sepolia"===e)return"sepolia";if("ethereum"===e||"mainnet"===e)return"ethereum";if("hardhat"===e)return"hardhat";throw new Error(`Unsupported EVM chain: "${t}". Supported chains: ${Object.values(r).join(", ")}`)}function l(t){return n[t]}function u(t){return a[t]}function d(t){return Math.floor(t/o)*o}function h(){return d(Math.floor(Date.now()/1e3))}async function p(){const t=Math.floor(Date.now()/1e3),e=t%o;if(!function(t){const e=(t??Math.floor(Date.now()/1e3))%o;return e>=92||e<8}(t))return;let r;r=e>=92?100-e+8:8-e,console.log(`[Quantum] Waiting ${r}s for safe moment (current time in quantum: ${e}s)`),await new Promise((t=>setTimeout(t,1e3*r))),console.log(`[Quantum] Safe quantum ready: ${h()}`)}0,0;var m=class{static verifyAmountPositionActionAuthorizationStructure(t){if(!t||"object"!=typeof t)throw new Error('Missing or invalid "auth" parameter - must be an object');if(!t.positionId)throw new Error("auth.positionId is required");if("number"!=typeof t.chainId)throw new Error("auth.chainId must be a number");if("number"!=typeof t.timestamp)throw new Error("auth.timestamp must be a number");if(void 0===t.amount||null===t.amount)throw new Error("auth.amount is required");if(!t.action)throw new Error("auth.action is required");if(!t.signature)throw new Error("auth.signature is required");if(!t.mode)throw new Error("auth.mode is required");if("dev"!==t.mode&&"prod"!==t.mode)throw new Error(`auth.mode must be "dev" or "prod", got: ${t.mode}`);return t}static async recoverSigner(t,e){try{const o=ethers.utils.arrayify(t),r=ethers.utils.hashMessage(o);return ethers.utils.recoverAddress(r,e)}catch(t){throw console.error("[Authorization] Signature recovery failed:",t),new Error(`Failed to recover signer: ${t instanceof Error?t.message:String(t)}`)}}static async verifyActionAuthorization(t,e,o){await p();try{!function(t,e){const o=d(e??Math.floor(Date.now()/1e3)),r=d(t);if(r!==o)throw new Error(`[Quantum] Invalid timestamp: signature from quantum ${r}, current quantum is ${o}`)}(t.timestamp)}catch(t){return console.error("[Authorization] Quantum timestamp validation failed:",t),!1}if(!t.mode)return console.error("[Authorization] Missing mode field"),!1;if("dev"!==t.mode&&"prod"!==t.mode)return console.error(`[Authorization] Invalid mode: ${t.mode}, must be "dev" or "prod"`),!1;if("prod"===t.mode)return console.error('[Authorization] Production mode not yet supported - use mode="dev"'),!1;const r=e();console.log("[Authorization] Message hash:",r),console.log("[Authorization] Recovering signer from signature..."),console.log(" signature:",t.signature);const i=await this.recoverSigner(r,t.signature);console.log(" recoveredAddress:",i),console.log("[Authorization] Checking authorization:"),console.log(" recoveredAddress:",i),console.log(" recoveredAddress (lowercase):",i.toLowerCase());const n=o(i);return console.log(" match:",n?"\u2705 YES":"\u274c NO"),n||console.error("[Authorization] Not authorized:",`\n Recovered: ${i}`),n}static async verifyAmountPositionActionAuthorization(t,e){return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action)),o="string"==typeof t.amount?BigInt(t.amount):t.amount;console.log("[Authorization] Building message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" amount (string):",t.amount),console.log(" amount (BigInt):",o.toString()),console.log(" action:",t.action),console.log(" actionHash:",e);const r=[t.positionId,t.timestamp,t.chainId,o.toString(),e],i=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","bytes32"],r);return console.log(" messageHash (solidityKeccak256):",i),i}),(t=>t.toLowerCase()===e.toLowerCase()))}static async verifyMintAuthorization(t,e){return"mint-ucd"!==t.action?(console.error(`[Authorization] Invalid action for mint: expected "mint-ucd", got "${t.action}"`),!1):this.verifyAmountPositionActionAuthorization(t,e)}static async verifyWithdrawAuthorization(t,e){if("withdraw-btc"!==t.action)return console.error(`[Authorization] Invalid action for withdrawal: expected "withdraw-btc", got "${t.action}"`),!1;return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action)),o="string"==typeof t.amount?BigInt(t.amount):t.amount;console.log("[Authorization] Building withdrawal message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" amount (string):",t.amount),console.log(" amount (BigInt):",o.toString()),console.log(" destinationAddress:",t.destinationAddress),console.log(" action:",t.action),console.log(" actionHash:",e);const r=[t.positionId,t.timestamp,t.chainId,o.toString(),t.destinationAddress,e],i=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","string","bytes32"],r);return console.log(" messageHash (solidityKeccak256):",i),i}),(t=>t.toLowerCase()===e.toLowerCase()))}static verifyTermPositionActionAuthorizationStructure(t){if(!t||"object"!=typeof t)throw new Error('Missing or invalid "auth" parameter - must be an object');if(!t.positionId)throw new Error("auth.positionId is required");if("number"!=typeof t.chainId)throw new Error("auth.chainId must be a number");if("number"!=typeof t.timestamp)throw new Error("auth.timestamp must be a number");if(void 0===t.newTerm||null===t.newTerm)throw new Error("auth.newTerm is required");if("number"!=typeof t.newTerm)throw new Error("auth.newTerm must be a number");if(!t.action)throw new Error("auth.action is required");if(!t.signature)throw new Error("auth.signature is required");if(!t.mode)throw new Error("auth.mode is required");if("dev"!==t.mode&&"prod"!==t.mode)throw new Error(`auth.mode must be "dev" or "prod", got: ${t.mode}`);return t}static async verifyTermPositionActionAuthorization(t,e){return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action));console.log("[Authorization] Building term message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" newTerm:",t.newTerm),console.log(" action:",t.action),console.log(" actionHash:",e);const o=[t.positionId,t.timestamp,t.chainId,t.newTerm,e],r=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","uint256","bytes32"],o);return console.log(" messageHash (solidityKeccak256):",r),r}),(t=>t.toLowerCase()===e.toLowerCase()))}static async verifyExtendAuthorization(t,e){return"extend-position"!==t.action?(console.error(`[Authorization] Invalid action for extension: expected "extend-position", got "${t.action}"`),!1):this.verifyTermPositionActionAuthorization(t,e)}static async verifyBtcExecutionAuthorization(t,e){if("execute-btc-withdrawal"!==t.action)return console.error(`[Authorization] Invalid action for BTC execution: expected "execute-btc-withdrawal", got "${t.action}"`),!1;return this.verifyActionAuthorization(t,(()=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.action));console.log("[Authorization] Building BTC execution message hash:"),console.log(" positionId:",t.positionId),console.log(" timestamp:",t.timestamp),console.log(" chainId:",t.chainId),console.log(" utxoIdentifier:",t.utxoIdentifier),console.log(" networkFee:",t.networkFee),console.log(" action:",t.action),console.log(" actionHash:",e);const o=[t.positionId,t.timestamp,t.chainId,t.utxoIdentifier,t.networkFee,e],r=ethers.utils.solidityKeccak256(["bytes32","uint256","uint256","string","uint256","bytes32"],o);return console.log(" messageHash (solidityKeccak256):",r),r}),(t=>t.toLowerCase()===e.toLowerCase()))}},g=class{stepStart(t,e){}stepEnd(t,e){}log(t,e){}warn(t,e){}error(t,e){}getElapsed(){return 0}getRemaining(){return 3e4}summary(){}getExecutionId(){return""}isDebugEnabled(){return!1}},w=class{constructor(t){this.stepStartTimes=new Map,this.stepDurations=new Map,this.TIMEOUT_MS=3e4,this.actionName=t,this.executionStartTime=Date.now();const e=Date.now(),o=Math.random().toString(36).substring(2,9);this.executionId=`exec_${e}_${o}`,this.logHeader()}logHeader(){console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution started`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`);void 0!==globalThis.litNodeContext?(console.log(`[${this.actionName}] Context: LIT Node`),console.log(`[${this.actionName}] Node: ${globalThis.litNodeContext?.nodeAddress||"unknown"}`)):console.log(`[${this.actionName}] Context: Browser/Test`),console.log(`[${this.actionName}] ========================================`)}stepStart(t,e){this.stepStartTimes.set(t,Date.now());const o=Date.now()-this.executionStartTime,r=this.TIMEOUT_MS-o;console.log(`[Step ${t}] ${e}...`),console.log(`[Step ${t}] Elapsed: ${o}ms | Remaining: ${r}ms`)}stepEnd(t,e=5e3){const o=this.stepStartTimes.get(t);if(!o)return console.warn(`[Step ${t}] \u26a0\ufe0f stepEnd called without matching stepStart`),void 0;const r=Date.now()-o;this.stepDurations.set(t,r),console.log(`[Step ${t}] Duration: ${r}ms`),r>e&&console.warn(`\u26a0\ufe0f [Step ${t}] Took ${r}ms (> ${e}ms threshold)`);const i=Date.now()-this.executionStartTime,n=this.TIMEOUT_MS-i;n<5e3&&console.warn(`\u26a0\ufe0f [Step ${t}] Time remaining: ${n}ms (approaching timeout)`)}log(t,e){console.log(`[Step ${t}] ${e}`)}warn(t,e){console.warn(`\u26a0\ufe0f [Step ${t}] ${e}`)}error(t,e){console.error(`\u274c [Step ${t}] ${e}`)}getElapsed(){return Date.now()-this.executionStartTime}getRemaining(){return this.TIMEOUT_MS-this.getElapsed()}summary(){const t=Date.now()-this.executionStartTime,e=this.TIMEOUT_MS-t;if(console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution Summary`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`),console.log(`[${this.actionName}] Total time: ${(t/1e3).toFixed(2)}s`),console.log(`[${this.actionName}] Time remaining: ${e}ms`),this.stepDurations.size>0){console.log(`[${this.actionName}] Step breakdown:`);for(const[e,o]of this.stepDurations.entries()){const r=(o/t*100).toFixed(1);console.log(`[${this.actionName}] Step ${e}: ${o}ms (${r}%)`)}}t>2e4&&console.warn(`\u26a0\ufe0f [${this.actionName}] Slow execution: ${(t/1e3).toFixed(2)}s (${e}ms remaining)`),e<5e3&&console.warn(`\u26a0\ufe0f [${this.actionName}] CRITICAL: Only ${e}ms remaining before timeout!`),console.log(`[${this.actionName}] ========================================`)}getExecutionId(){return this.executionId}isDebugEnabled(){return!0}},f=class{static create(t="LIT Action",e){return e??globalThis.debugAction??!1?new w(t):new g}},y=class{constructor(t){this.config=t,this.timeout=t.timeout||15e3,this.logger=f.create("BitcoinDataProvider")}async getCurrentBlockHeight(){if(this.logger.stepStart("0","getCurrentBlockHeight"),this.config.rpcHelper){const t=await this.config.rpcHelper.getBlockCount();return this.logger.stepEnd("0"),t}const t=new AbortController,e=setTimeout((()=>t.abort()),this.timeout);try{const o=await fetch(`${this.config.providerUrl}/blocks/tip/height`,{signal:t.signal,headers:{Accept:"text/plain","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(e),!o.ok)throw new Error(`Failed to fetch block height: ${o.status} ${o.statusText}`);const r=await o.text(),i=parseInt(r.trim(),10);if(isNaN(i))throw new Error(`Invalid block height response: ${r}`);return this.logger.stepEnd("0"),i}catch(t){if(clearTimeout(e),this.logger.stepEnd("0"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOs(t){this.logger.log("0",`Original address: "${t}"`);const e=this.stripNetworkPrefix(t);if(this.logger.log("0",`Cleaned address: "${e}"`),this.config.rpcHelper)return await this.fetchUTXOsFromRPC(e);try{return await this.fetchUTXOsFromProvider(this.config.providerUrl,e)}catch(t){if(this.logger.warn("0",`Primary provider failed: ${t.message}`),this.config.fallbackProviders&&this.config.fallbackProviders.length>0){const t=[...this.config.fallbackProviders].sort(((t,e)=>t.priority-e.priority));for(const o of t)try{return this.logger.log("0",`Trying fallback: ${o.name} (${o.url})`),await this.fetchUTXOsFromProvider(o.url,e)}catch(t){this.logger.warn("0",`Fallback ${o.name} failed: ${t.message}`);continue}}throw new Error(`Failed to fetch UTXOs: ${t.message}`)}}stripNetworkPrefix(t){return t.replace(/^(REGTEST_|TESTNET_|MAINNET_)/,"")}async fetchUTXOsFromRPC(t){if(!this.config.rpcHelper)throw new Error("RPC helper not configured");const e=this.config.rpcWallet||"";return(await this.config.rpcHelper.listUnspent(e,t)).map((t=>({txid:t.txid,vout:t.vout,satoshis:BigInt(Math.round(1e8*t.amount)),confirmations:t.confirmations})))}parseBlockstreamUTXOs(t,e){if(!Array.isArray(t))throw new Error("Invalid Blockstream response format: expected an array");return t.map((t=>{let o=0;return t.status&&"object"==typeof t.status?t.status.confirmed&&(o=void 0!==t.status.block_height&&t.status.block_height>0?e-t.status.block_height+1:1):void 0!==t.confirmations&&(o=t.confirmations),{txid:t.txid,vout:void 0!==t.vout?t.vout:t.n,satoshis:BigInt(void 0!==t.value?t.value:t.satoshis),confirmations:o}}))}async fetchUTXOsFromProvider(t,e){this.logger.stepStart("1","fetchUTXOsFromProvider");const o=`${t}/address/${e}/utxos`;this.logger.log("1",`Fetching UTXOs from ${o}`);const r=await this.getCurrentBlockHeight();this.logger.log("1",`Current block height: ${r}`);const i=new AbortController,n=setTimeout((()=>i.abort()),this.timeout);try{const t=await fetch(o,{signal:i.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(n),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();this.logger.log("1",`Raw API Response: ${JSON.stringify(e,null,2)}`);const a=this.parseBlockstreamUTXOs(e,r);return this.logger.stepEnd("1"),a}catch(t){if(clearTimeout(n),this.logger.stepEnd("1"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOSet(t,e){let o=[],r=null;for(let e=1;e<=3;e++)try{this.logger.log("2",`UTXO query attempt ${e}/3...`),o=await this.getUTXOs(t),this.logger.log("2",`UTXO query succeeded on attempt ${e}`),r=null;break}catch(t){r=t,this.logger.error("2",`UTXO query failed on attempt ${e}: ${t.message}`),e<3&&(this.logger.log("2","Waiting 500ms before retry..."),await new Promise((t=>setTimeout(t,500))))}if(r)throw new Error(`Failed to query UTXOs after 3 attempts: ${r.message}`);const i=o.reduce(((t,e)=>t+e.satoshis),0n),n=o.filter((t=>t.confirmations>=e)).reduce(((t,e)=>t+e.satoshis),0n),a=i-n;return{utxos:o,totalBalance:i,totalUTXOs:o.length,confirmedBalance:n,unconfirmedBalance:a}}async getBalance(t){const e=this.stripNetworkPrefix(t),o=`${this.config.providerUrl}/address/${e}`;this.logger.log("3",`Fetching balance from ${o}`);const r=new AbortController,i=setTimeout((()=>r.abort()),this.timeout);try{const t=await fetch(o,{signal:r.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(i),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();if(!e.chain_stats)throw new Error("Invalid response: missing chain_stats");const n=e.chain_stats.funded_txo_sum,a=e.chain_stats.spent_txo_sum;if(void 0===n||void 0===a)throw new Error(`Invalid response: missing required fields. funded_txo_sum: ${n}, spent_txo_sum: ${a}`);const s=BigInt(n-a);return this.logger.log("3",`Balance: ${s.toString()} sats`),this.logger.log("3",` - Funded: ${n} sats, Spent: ${a} sats`),s}catch(t){if(clearTimeout(i),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);const o=t.message||"Unknown error";throw this.logger.log("3",`Balance fetch failed: ${o}`),new Error(`Failed to fetch Bitcoin balance for address ${e}: ${o}`)}}async getTransaction(t){if(this.config.rpcHelper)try{const e=this.config.rpcWallet||"",o=await this.config.rpcHelper.getTransaction(e,t);return{txid:o.txid,confirmations:o.confirmations||0}}catch(t){if(t.message?.includes("Invalid or non-wallet transaction")||-5===t.code)return null;throw t}const e=new AbortController,o=setTimeout((()=>e.abort()),this.timeout);try{const r=await fetch(`${this.config.providerUrl}/tx/${t}`,{signal:e.signal});if(clearTimeout(o),!r.ok){if(404===r.status)return null;throw new Error(`Bitcoin provider error: ${r.status} ${r.statusText}`)}const i=await r.json();return i.status?{txid:i.txid,confirmations:i.status.confirmed&&i.status.block_height?1:0}:null}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);if(t.message?.includes("404"))return null;throw t}}};function b(t,e){if(0===t)return{termDurationDays:0,termLengthDays:30*e,isExpired:!1,daysUntilExpiry:30*e,daysIntoGracePeriod:0};const o=30*e,r=Math.floor(Date.now()/1e3)-t,i=Math.floor(r/86400);return{termDurationDays:i,termLengthDays:o,isExpired:i>o,daysUntilExpiry:Math.max(0,o-i),daysIntoGracePeriod:Math.max(0,i-o)}}function T(t,e){if(!t)return 13e3;const o=Math.min(e,30);return 11e3+9e3*(o*o)/900}function v(t,o,r){const i=t*o/10000000000000000n;if(0n===r)return{collateralValueUsd:i,collateralRatioBps:Number.MAX_SAFE_INTEGER};return{collateralValueUsd:i,collateralRatioBps:Number(i*e*10000n/r)}}function A(t,e){if(t<0n)throw new Error("Collateral value cannot be negative");if(e<=0n)throw new Error("Debt must be positive to calculate ratio");const o=Number(10000n*(10000000000n*t)/e);if(o<0)throw new Error(`Collateral ratio out of bounds: ${o} bps (${o/100}%)`);return!isFinite(o)||o>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:o}function E(t){return t/100}var S=class{constructor(t,e,o){this.mode=o,e&&Array.isArray(e)?this.sources=e:t&&Array.isArray(t)?this.sources=this.buildSources(t):this.sources=this.buildSources(),this.sources.sort(((t,e)=>t.priority-e.priority)),this.validateProviderCount()}validateProviderCount(){if("prod"===this.mode&&this.sources.length<3)throw new Error(`Production mode requires at least 3 price providers for consensus. Currently configured: ${this.sources.length} provider(s). Supported providers: CoinGecko, Binance, Coinbase, CryptoCompare (with API key)`);if(0===this.sources.length)throw new Error("No price providers configured. At least one provider is required.")}buildSources(t){const e=[];if(!t||0===t.length)return this.getDefaultSources();let o=1;for(const r of t){switch(r.name.toLowerCase()){case"coingecko":e.push(this.createCoinGeckoSource(r.apiKey,o++));break;case"binance":e.push(this.createBinanceSource(r.apiKey,r.apiSecret,o++));break;case"coinbase":e.push(this.createCoinbaseSource(r.apiKey,o++));break;case"cryptocompare":if(!r.apiKey)throw new Error("CryptoCompare requires an API key. Please provide apiKey in priceProviders config.");e.push(this.createCryptoCompareSource(r.apiKey,o++));break;default:console.warn(`[Price Oracle] Unknown provider: ${r.name} - skipping`)}}return e}createCryptoCompareSource(t,e){return{name:"CryptoCompare",fetchPrice:async()=>{const e=new URL("https://min-api.cryptocompare.com/data/price");e.searchParams.set("fsym","BTC"),e.searchParams.set("tsyms","USDT"),e.searchParams.set("api_key",t);const o=await(async t=>{const e=new AbortController,o=setTimeout((()=>e.abort()),5e3);try{const r=await fetch(t,{signal:e.signal,headers:{Accept:"application/json"}});if(clearTimeout(o),!r.ok)throw new Error(`HTTP ${r.status} ${r.statusText}`);return await r.json()}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error("Request timeout after 5000ms");throw t}})(e.toString()),r=Number(o?.USDT);if(!Number.isFinite(r)||r<=0)throw new Error("Invalid CryptoCompare price payload");const i=(await Lit.Actions.broadcastAndCollect({name:"cryptoComparePrice",value:r.toString()})).map((t=>parseFloat(t)));return i.sort(((t,e)=>t-e)),i[Math.floor(i.length/2)]},priority:e}}createFetchJson(){return async(t,e)=>{const o=new AbortController,r=setTimeout((()=>o.abort()),5e3);try{const r=await fetch(t,{headers:{Accept:"application/json",...e||{}},signal:o.signal});if(!r.ok)throw new Error(`HTTP ${r.status} ${r.statusText}`);return await r.json()}finally{clearTimeout(r)}}}getDefaultSources(){return[this.createCoinGeckoSource(void 0,1),this.createBinanceSource(void 0,void 0,2),this.createCoinbaseSource(void 0,3)]}createCoinGeckoSource(t,e=1){const o=this.createFetchJson();return{name:"CoinGecko",fetchPrice:async()=>{const t=new URL("https://api.coingecko.com/api/v3/simple/price");t.searchParams.set("ids","bitcoin"),t.searchParams.set("vs_currencies","usd");const e=await o(t.toString()),r=Number(e?.bitcoin?.usd);if(!Number.isFinite(r)||r<=0)throw new Error("Invalid CoinGecko price payload");const i=(await Lit.Actions.broadcastAndCollect({name:"coinGeckoPrice",value:r.toString()})).map((t=>parseFloat(t)));return i.sort(((t,e)=>t-e)),i[Math.floor(i.length/2)]},priority:e}}createBinanceSource(t,e,o=2){const r=this.createFetchJson();return{name:"Binance",fetchPrice:async()=>{const e=new URL("https://api.binance.com/api/v3/ticker/price");e.searchParams.set("symbol","BTCUSDT");const o=t?{"X-MBX-APIKEY":t}:void 0,i=await r(e.toString(),o),n=Number(i?.price);if(!Number.isFinite(n)||n<=0)throw new Error("Invalid Binance price payload");const a=(await Lit.Actions.broadcastAndCollect({name:"binancePrice",value:n.toString()})).map((t=>parseFloat(t)));return a.sort(((t,e)=>t-e)),a[Math.floor(a.length/2)]},priority:o}}createCoinbaseSource(t,e=3){const o=this.createFetchJson();return{name:"Coinbase",fetchPrice:async()=>{const e=t?{"CB-ACCESS-KEY":t}:void 0,r=await o("https://api.coinbase.com/v2/prices/BTC-USD/spot",e),i=Number(r?.data?.amount);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid Coinbase price payload");const n=(await Lit.Actions.broadcastAndCollect({name:"coinbasePrice",value:i.toString()})).map((t=>parseFloat(t)));return n.sort(((t,e)=>t-e)),n[Math.floor(n.length/2)]},priority:e}}async getBTCPrice(){const t=Date.now();console.log("[Price Oracle] Fetching BTC price from external sources (parallel)..."),console.log(`[Price Oracle] Start time: ${t}`);const e=new Promise(((e,o)=>{setTimeout((()=>{const e=Date.now()-t;console.error(`[Price Oracle] \u274c GLOBAL TIMEOUT after ${e}ms`),o(new Error("Price oracle global timeout after 15000ms"))}),15e3)})),o=this.sources.map((async t=>{const e=Date.now();try{console.log(`[Price Oracle] [${e}] Querying ${t.name}...`);const o=await t.fetchPrice(),r=Date.now()-e;return console.log(`[Price Oracle] \u2705 [+${r}ms] ${t.name}: $${o.toLocaleString()}`),{source:t.name,priceUSD:o}}catch(o){const r=Date.now()-e;throw console.warn(`[Price Oracle] \u26a0\ufe0f [+${r}ms] ${t.name} failed: ${o.message}`),o}}));try{console.log("[Price Oracle] Waiting for first success (race pattern)...");const r=await Promise.race([Promise.race(o.map((t=>t.catch((t=>({error:t})))))),e]);if("error"in r){console.log("[Price Oracle] First result was error, waiting for any success...");const e=Date.now(),r=new Promise(((t,o)=>{setTimeout((()=>{const t=Date.now()-e;console.error(`[Price Oracle] \u274c FALLBACK TIMEOUT after ${t}ms`),o(new Error("Price oracle fallback timeout after 10000ms"))}),1e4)})),i=await Promise.race([Promise.allSettled(o),r]),n=Date.now()-e;console.log(`[Price Oracle] Fallback completed in ${n}ms`);const a=i.find((t=>"fulfilled"===t.status));if(!a){const e=Date.now()-t;throw console.error(`[Price Oracle] All sources failed after ${e}ms`),new Error("All price sources failed")}const{source:s,priceUSD:c}=a.value,l=Date.now()-t;console.log(`[Price Oracle] Using ${s} (first successful after failures) - total time: ${l}ms`);const u=Math.round(100*c),d=1000000n*BigInt(u);return console.log(`[Price Oracle] Price with 8 decimals: ${d}`),d}const{source:i,priceUSD:n}=r,a=Date.now()-t;console.log(`[Price Oracle] Using ${i} (fastest response) - total time: ${a}ms`);const s=Math.round(100*n),c=1000000n*BigInt(s);return console.log(`[Price Oracle] Price with 8 decimals: ${c}`),c}catch(e){const o=Date.now()-t;throw console.error(`[Price Oracle] Failed after ${o}ms: ${e.message}`),new Error(`All price sources failed: ${e.message}`)}}async getBTCPriceConsensus(){console.log("[Price Oracle] Fetching BTC price with consensus...");const t=(await Promise.allSettled(this.sources.map((async t=>{const e=await t.fetchPrice();return{source:t.name,price:e}})))).filter((t=>"fulfilled"===t.status)).map((t=>t.value));if(0===t.length)throw new Error("No price sources returned data");console.log(`[Price Oracle] Got prices from ${t.length}/${this.sources.length} sources:`),t.forEach((t=>{console.log(` ${t.source}: $${t.price.toLocaleString()}`)}));const e=t.map((t=>t.price));e.sort(((t,e)=>t-e));const o=e[Math.floor(e.length/2)],r=e[0],i=e[e.length-1]/r,n=t.filter((t=>Math.abs(t.price-o)/o<=.02));if(i>1.05&&n.length===t.length)throw new Error(`Price consensus failed: sources too dispersed (${(100*(i-1)).toFixed(1)}% spread)`);if(n.length<t.length){const e=t.filter((t=>!n.find((e=>e.source===t.source))));if(console.log(`[Price Oracle] \u26a0\ufe0f Detected ${e.length} outlier(s):`),e.forEach((t=>{const e=Math.abs(t.price-o)/o;console.log(` ${t.source}: $${t.price.toLocaleString()} (${(100*e).toFixed(1)}% deviation)`)})),n.length<2)throw new Error("Price consensus failed: insufficient valid sources after outlier removal");console.log(`[Price Oracle] \u2705 Outliers filtered, continuing with ${n.length} valid sources`);const r=n.map((t=>t.price));r.sort(((t,e)=>t-e));const i=r[Math.floor(r.length/2)];console.log(`[Price Oracle] \u2705 Consensus price (median): $${i.toLocaleString()}`);const a=Math.round(100*i);return 1000000n*BigInt(a)}console.log(`[Price Oracle] \u2705 Consensus price (median): $${o.toLocaleString()}`);const a=Math.round(100*o);return 1000000n*BigInt(a)}},$=class{constructor(t){this.config=t,this.bitcoinProvider=t.bitcoinProvider}async calculateBalance(t,e){const o=await this.bitcoinProvider.getBalance(e),r=await this.getAuthorizedSpendsFromContract(t),i=r.reduce(((t,e)=>t+e.satoshis),0n),n=o>i?o-i:0n;return{totalUTXOs:[],totalBalance:o,authorizedUTXOs:r,authorizedBalance:i,authorizedSpendsHash:this.computeAuthorizedSpendsHash(t,r),availableUTXOs:[],availableBalance:n,vaultAddress:e,positionId:t,timestamp:Date.now()}}async calculateTrustedBalance(t,e,o){if(o)return await this.calculateBalance(t,e);const r=this.config.minConfirmations||6,i=await this.bitcoinProvider.getUTXOSet(e,r),n=await this.getAuthorizedSpendsFromContract(t);for(const t of n){if(i.utxos.some((e=>e.txid===t.txid&&e.vout===t.vout)))throw new Error(`Authorized UTXO ${t.txid}:${t.vout} not yet spent. Transaction was authorized in smart contract but not signed and broadcasted to Bitcoin network. Complete the transaction by signing and broadcasting it.`)}const a=n.reduce(((t,e)=>t+e.satoshis),0n),s=i.utxos.filter((t=>!this.isUTXOAuthorized(t,n))),c=s.reduce(((t,e)=>t+e.satoshis),0n),l=this.computeAuthorizedSpendsHash(t,n);return{totalUTXOs:i.utxos,totalBalance:i.totalBalance,authorizedUTXOs:n,authorizedBalance:a,authorizedSpendsHash:l,availableUTXOs:s,availableBalance:c,vaultAddress:e,positionId:t,timestamp:Date.now()}}async getTrustedBalance(t,e){return(await this.calculateTrustedBalance(t,e)).availableBalance}async getAvailableUTXOs(t,e){return(await this.calculateTrustedBalance(t,e)).availableUTXOs}async isUTXOAvailable(t,e,o,r){return(await this.getAvailableUTXOs(t,e)).some((t=>t.txid===o&&t.vout===r))}isUTXOAuthorized(t,e){return e.some((e=>e.txid===t.txid&&e.vout===t.vout))}async getAuthorizedSpendsFromContract(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let o;o=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const r=new ethers.providers.StaticJsonRpcProvider(o,this.config.chainId),i=new ethers.Contract(this.config.contractAddress,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getAuthorizedSpends",outputs:[{components:[{internalType:"string",name:"txid",type:"string"},{internalType:"uint32",name:"vout",type:"uint32"},{internalType:"uint256",name:"satoshis",type:"uint256"},{internalType:"string",name:"targetAddress",type:"string"},{internalType:"uint256",name:"targetAmount",type:"uint256"},{internalType:"uint256",name:"authorizedAt",type:"uint256"}],internalType:"struct LoanOperationsManager.AuthorizedSpend[]",name:"",type:"tuple[]"}],stateMutability:"view",type:"function"}],r);return(await i.getAuthorizedSpends(e)).map((e=>({txid:e.txid,vout:Number(e.vout),satoshis:BigInt(e.satoshis.toString()),positionId:t,targetAddress:e.targetAddress,targetAmount:BigInt(e.targetAmount.toString()),timestamp:Number(e.authorizedAt)})))}computeAuthorizedSpendsHash(t,e){const o=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;if(0===e.length)return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[o,[]]));const r=e.map((t=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.txid)),o=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.targetAddress));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","uint32","uint256","bytes32","uint256","uint256"],[e,t.vout,t.satoshis.toString(),o,t.targetAmount.toString(),t.timestamp]))}));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[o,r]))}};var I=class{constructor(t){this.termManagerAddress=t.termManagerAddress,this.loanOpsManagerAddress=t.loanOpsManagerAddress,this.chain=t.chain,this.chainId=t.chainId,this.rpcUrl=t.rpcUrl}async getLiquidationThreshold(){try{let t;t=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const e=new ethers.providers.StaticJsonRpcProvider(t,{name:"any",chainId:this.chainId}),o=[{inputs:[],name:"liquidationThreshold",outputs:[{internalType:"uint256",name:"",type:"uint256"}],stateMutability:"view",type:"function"}],r=new ethers.Contract(this.loanOpsManagerAddress,o,e),i=await r.liquidationThreshold();return Number(i.toString())}catch(t){throw console.error("[ProtocolParameters] Error fetching liquidation threshold:",t.message),new Error(`Failed to fetch liquidation threshold: ${t.message}`)}}async getTermFees(t){try{let e;e=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const o=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),r=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint88",name:"originationFee",type:"uint88"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],i=new ethers.Contract(this.termManagerAddress,r,o),n=await i.getTermFees(t);return{originationFeeBps:Number(n.originationFee?.toString?.()??n[0]?.toString?.()??n[0]),extensionFeeBps:Number(n.extensionFee?.toString?.()??n[1]?.toString?.()??n[1])}}catch(t){throw console.error("[ProtocolParameters] Error fetching term fees:",t.message),new Error(`Failed to fetch term fees: ${t.message}`)}}async getAuthorizedSpendsHash(t){try{const e=this.rpcUrl||await Lit.Actions.getRpcUrl({chain:this.chain}),o=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),r=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`,i=new ethers.Contract(this.loanOpsManagerAddress,[{inputs:[{name:"positionId",type:"bytes32"}],name:"getAuthorizedSpendsHash",outputs:[{name:"",type:"bytes32"}],stateMutability:"view",type:"function"}],o);return await i.getAuthorizedSpendsHash(r)}catch(t){throw console.error("[ProtocolParameters] Error fetching authorized spends hash:",t.message),new Error(`Failed to fetch authorized spends hash: ${t.message}`)}}};function P(t){return new I(t)}var x=class{constructor(t){this.config=t}async getVaultSnapshot(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const o=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress),r=await this.config.priceOracle.getBTCPrice(),i=P({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[n,a]=await Promise.all([i.getLiquidationThreshold(),i.getTermFees(e.selectedTerm)]),s=b(e.termStartTimestamp,e.selectedTerm),c=T(s.isExpired,s.daysIntoGracePeriod),l=v(o.availableBalance,r,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:o.totalBalance,totalUTXOs:o.totalUTXOs,authorizedSpendsSats:o.authorizedBalance,authorizedSpendsHash:o.authorizedSpendsHash,availableBTCSats:o.availableBalance,availableUTXOs:o.availableUTXOs,btcPriceUsd:r,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:s.termDurationDays,termLengthDays:s.termLengthDays,isExpired:s.isExpired,daysUntilExpiry:s.daysUntilExpiry,daysIntoGracePeriod:s.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:n,originationFeeBps:a.originationFeeBps,extensionFeeBps:a.extensionFeeBps,timestamp:Date.now()}}async getVaultSnapshotFast(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const o=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress,!0),r=await this.config.priceOracle.getBTCPrice(),i=P({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[n,a]=await Promise.all([i.getLiquidationThreshold(),i.getTermFees(e.selectedTerm)]),s=b(e.termStartTimestamp,e.selectedTerm),c=T(s.isExpired,s.daysIntoGracePeriod),l=v(o.availableBalance,r,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:o.totalBalance,totalUTXOs:o.totalUTXOs,authorizedSpendsSats:o.authorizedBalance,authorizedSpendsHash:o.authorizedSpendsHash,availableBTCSats:o.availableBalance,availableUTXOs:o.availableUTXOs,btcPriceUsd:r,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:s.termDurationDays,termLengthDays:s.termLengthDays,isExpired:s.isExpired,daysUntilExpiry:s.daysUntilExpiry,daysIntoGracePeriod:s.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:n,originationFeeBps:a.originationFeeBps,extensionFeeBps:a.extensionFeeBps,timestamp:Date.now()}}async queryPositionState(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let o;o=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const r=new ethers.providers.StaticJsonRpcProvider(o,this.config.chainId),i=new ethers.Contract(this.config.contractAddress,[{inputs:[],name:"core",outputs:[{internalType:"address",name:"",type:"address"}],stateMutability:"view",type:"function"}],r),n=await i.core(),a=new ethers.Contract(n,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getPositionDetails",outputs:[{components:[{internalType:"bytes32",name:"positionId",type:"bytes32"},{internalType:"bytes32",name:"pkpId",type:"bytes32"},{internalType:"uint256",name:"ucdDebt",type:"uint256"},{internalType:"string",name:"vaultAddress",type:"string"},{internalType:"address",name:"borrower",type:"address"},{internalType:"uint40",name:"createdAt",type:"uint40"},{internalType:"uint40",name:"lastUpdated",type:"uint40"},{internalType:"uint16",name:"selectedTerm",type:"uint16"},{internalType:"uint40",name:"expiryAt",type:"uint40"},{internalType:"enum LoanStatusLib.LoanStatus",name:"status",type:"uint8"}],internalType:"struct IPositionManagerCore.Position",name:"",type:"tuple"}],stateMutability:"view",type:"function"}],r),s=await a.getPositionDetails(e),l=Number(s.status);if(l<0||l>7)throw console.error("[VaultSnapshot] INVALID STATUS - ABI decoding error detected"),console.error(" Position ID:",e),console.error(" Contract:",this.config.contractAddress),console.error(" Status decoded:",l,"(expected 0-7)"),console.error(" Full position:",JSON.stringify(s,null,2)),new Error(`Invalid position status decoded from contract: ${l} (expected 0-7). This indicates an ABI decoding issue. Position: ${e}`);const u=Number(s.expiryAt),d=Number(s.selectedTerm),h=(m=d,0===(p=u)?0:p-30*m*86400);var p,m;return{positionId:s.positionId,pkpId:s.pkpId,borrower:s.borrower,vaultAddress:this.parseVaultAddress(s.vaultAddress,c(this.config.chain)),ucdDebt:BigInt(s.ucdDebt.toString()),termStartTimestamp:h,selectedTerm:d,status:l,expiryAt:u}}parseVaultAddress(t,e){try{const o=JSON.parse(t);if("object"==typeof o&&null!==o){if("sepolia"===e.toLowerCase())return o.regtest||o.testnet||o.mainnet;return"testnet"===l(e)&&(o.testnet||o.regtest)||o.mainnet}}catch(t){}return t}async isLiquidatable(t){return(await this.getVaultSnapshot(t)).isLiquidatable}async getCollateralRatio(t){return(await this.getVaultSnapshot(t)).collateralRatioBps}async getAvailableBalance(t){return(await this.getVaultSnapshot(t)).availableBTCSats}async hasSufficientCollateral(t,o,r){const i=await this.getVaultSnapshot(t);return function(t,o,r,i){const n=o+r;return 0n===n||Number(t*e*10000n/n)>=i}(i.collateralValueUsd,i.ucdDebt,o,r)}};var B=1000000000000n,C=100000000000000n,U=[{inputs:[{internalType:"uint256",name:"term",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint256",name:"originationFee",type:"uint256"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],k=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"isValidTerm",outputs:[{internalType:"bool",name:"",type:"bool"}],stateMutability:"view",type:"function"}],D=[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"isExtensionPending",outputs:[{internalType:"bool",name:"",type:"bool"}],stateMutability:"view",type:"function"}];(async()=>{let e="0";try{console.log("[Extend Position Validator] Starting..."),console.log("[Extend Position Validator] DEBUG: globalThis.priceProviders:",globalThis.priceProviders),console.log("[Extend Position Validator] DEBUG: priceProviders type:",typeof globalThis.priceProviders),globalThis.priceProviders&&(console.log("[Extend Position Validator] DEBUG: priceProviders isArray:",Array.isArray(globalThis.priceProviders)),console.log("[Extend Position Validator] DEBUG: priceProviders length:",globalThis.priceProviders.length),globalThis.priceProviders.length>0&&console.log("[Extend Position Validator] DEBUG: First provider:",globalThis.priceProviders[0])),e="0";const r=globalThis.chain,n=globalThis.bitcoinProviderUrl,a=globalThis.selectedTerm;if(!r)throw new Error('Missing required parameter: "chain"');if(!n)throw new Error('Missing required parameter: "bitcoinProviderUrl"');if(!a||"number"!=typeof a)throw new Error('Missing required parameter: "selectedTerm"');const d=m.verifyTermPositionActionAuthorizationStructure(globalThis.auth),p=d.mode;let g,w,f,b,T,v,I;if("prod"===p?(w=c(r),f=l(w),g=function(t){for(const e of Object.values(i)){const o=s[e].find((e=>e.url===t));if(o)return o}const e=Object.entries(s).flatMap((([t,e])=>e.map((e=>`${t}: ${e.name} (${e.url})`)))).join(", ");throw new Error(`Bitcoin provider not approved. Provided URL: ${t}. Approved providers: ${e}`)}(n)):(w=r,f="sepolia"===r?"testnet":"regtest",g={name:"Custom Provider",url:n,minConfirmations:1,network:f}),"dev"!==p){u(w);throw new Error("Production mode contract addresses not yet configured. Use dev mode for testing.")}if(!globalThis.contractAddresses||"object"!=typeof globalThis.contractAddresses)throw new Error('Dev mode requires "contractAddresses" in globalThis');if(b=globalThis.contractAddresses.PositionManager,T=globalThis.contractAddresses.LoanOperationsManagerModule,v=globalThis.contractAddresses.TermManagerModule,I=globalThis.contractAddresses.UCDController,!(b&&T&&v&&I))throw new Error("Missing required contract addresses");const P=globalThis.customRpcUrl,M="prod"===p?u(w):d.chainId,O=P?new ethers.providers.JsonRpcProvider(P):new ethers.providers.JsonRpcProvider("sepolia"===r?"https://rpc.sepolia.org":"https://eth.llamarpc.com"),F=new y({providerUrl:g.url,network:g.network}),N=new S(globalThis.priceProviders,void 0),z=(o={contractAddress:T,chain:r,chainId:M,rpcUrl:P,bitcoinProvider:F,minConfirmations:"prod"===p?g.minConfirmations:1},new $(o));e="1";const L=function(t){return new x(t)}({contractAddress:b,termManagerAddress:v,loanOpsManagerAddress:T,chain:r,chainId:M,rpcUrl:P,vaultBalance:z,priceOracle:N});console.log("[Extend Position Validator] Using fast balance path (skipUTXOValidation: true)");const R=await L.getVaultSnapshot(d.positionId,!0),q=await N.getBTCPrice(),X=BigInt(q.toString());if(X<B||X>C)throw new Error(`Bitcoin price ${X} is outside acceptable range (1000000000000 - 100000000000000)`);e="2";if(!await m.verifyExtendAuthorization(d,R.borrower))throw new Error("Caller not authorized");e="3";if(![2,3].includes(R.status))throw new Error(`Invalid position status for extension: ${function(t){switch(t){case 0:return"PENDING_DEPOSIT";case 1:return"PENDING_MINT";case 2:return"ACTIVE";case 3:return"EXPIRED";case 4:return"LIQUIDATABLE";case 5:return"LIQUIDATED";case 6:return"REPAID";case 7:return"CLOSED";default:return`UNKNOWN(${t})`}}(R.status)}. Must be ACTIVE or EXPIRED.`);const H=d.positionId.startsWith("0x")?d.positionId:`0x${d.positionId.padStart(64,"0")}`,_=new ethers.Contract(b,[...D],O),V=new ethers.Contract(v,[...k,...U],O),[G,j]=await Promise.all([_.isExtensionPending(H),V.isValidTerm(a)]);if(G)throw new Error("Extension already in progress for this position. Please wait for the current extension to complete.");if(!j)throw new Error(`Invalid extension term: ${a} months. This term is not configured in TermManager.`);if(R.isLiquidatable)throw new Error("Position is liquidatable - extension rejected. Add collateral first.");const K=R.selectedTerm,J=K+a,W=65535;if(J>W)throw new Error(`Extension would cause term overflow: ${K} + ${a} = ${J} months, exceeds max ${W} months`);const Q=Math.floor(Date.now()/1e3),Y=86400,Z=12===a?365*Y:a*30*Y,tt=R.expiryAt+Z,et=3===R.status;if(tt-Q>2*Z)throw new Error("Invalid requested expiry date: cannot extend beyond double term period from now");if(et&&tt<=Q)throw new Error("Invalid requested expiry date: extension must extend into the future for expired loans");e="4";const[,ot]=await V.getTermFees(a),rt=BigInt(ot.toString());e="5";const it=R.ucdDebt*rt/10000n,nt=R.ucdDebt+it;e="6";const at=function(e,o){if(e<0n)throw new Error("BTC amount cannot be negative");if(o<=0n)throw new Error("BTC price must be positive");return e*o/t}(R.availableBTCSats,X),st=A(at,nt);if(!function(t,e,o){if(o<0||o>1e6)throw new Error("Minimum ratio must be between 0 and 1000000 bps (0% to 10000%)");return A(t,e)>=o}(at,nt,R.liquidationThresholdBps))throw new Error(`Insufficient collateral after extension: ${E(st)}% < ${E(R.liquidationThresholdBps)}%`);e="7";const ct=10,lt=h()+ct,ut=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256","uint256","uint256"],[d.positionId,a,X.toString(),R.availableBTCSats.toString(),lt]),dt=ethers.utils.keccak256(ut),ht=globalThis.publicKey;if(!ht)throw new Error("Missing required parameter: publicKey");e="8";const pt=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(dt),publicKey:"0x"+ht,sigName:"extendPositionAuth"});Lit.Actions.setResponse({response:JSON.stringify({approved:!0,positionId:d.positionId,selectedTerm:a,btcPrice:X.toString(),availableBTCBalance:R.availableBTCSats.toString(),extensionFee:it.toString(),newTotalDebt:nt.toString(),newCollateralRatioBps:st.toString(),signature:pt,timestamp:d.timestamp,quantumTimestamp:lt,validatorPkp:ht})})}catch(t){console.error(`[Extend Position Validator] Failed at step ${e}:`,t.message),Lit.Actions.setResponse({response:JSON.stringify({approved:!1,reason:t.message||t.toString(),failedStep:e,positionId:globalThis.auth?.positionId,timestamp:Date.now()})})}var o})()})();
@@ -1 +1 @@
1
- 5324bdbab36af9a57ce6799bbb492a020bf6fadaaf19ee62e844458c08e65433
1
+ 856564db12f1f6f63862f3d6ddd727a6c1f953a89fe5853c78c254af98d6a7a8
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "actionName": "extend-position-validator",
3
- "originalSize": 112413,
4
- "minifiedSize": 46265,
5
- "compressionRatio": 0.5884372803857205,
6
- "hash": "5324bdbab36af9a57ce6799bbb492a020bf6fadaaf19ee62e844458c08e65433",
7
- "buildTime": 1768406363047,
3
+ "originalSize": 112365,
4
+ "minifiedSize": 46223,
5
+ "compressionRatio": 0.5886352511903172,
6
+ "hash": "856564db12f1f6f63862f3d6ddd727a6c1f953a89fe5853c78c254af98d6a7a8",
7
+ "buildTime": 1768804917964,
8
8
  "version": "0.1.0"
9
9
  }
@@ -1 +1 @@
1
- var _LIT_ACTION_=(()=>{var t=1000000000000000000n,e=100;function r(t){switch(t){case 0:return"PENDING_DEPOSIT";case 1:return"PENDING_MINT";case 2:return"ACTIVE";case 3:return"EXPIRED";case 4:return"LIQUIDATABLE";case 5:return"LIQUIDATED";case 6:return"REPAID";case 7:return"CLOSED";default:return`UNKNOWN(${t})`}}var i=(t=>(t.SEPOLIA="sepolia",t.ETHEREUM="ethereum",t.HARDHAT="hardhat",t))(i||{}),o={sepolia:"testnet",ethereum:"mainnet",hardhat:"regtest"},a={ethereum:1,sepolia:11155111,hardhat:31337};function s(t){const e=t.toLowerCase().trim();if("sepolia"===e)return"sepolia";if("ethereum"===e||"mainnet"===e)return"ethereum";if("hardhat"===e)return"hardhat";throw new Error(`Unsupported EVM chain: "${t}". Supported chains: ${Object.values(i).join(", ")}`)}function n(t){return a[t]}var c=class{stepStart(t,e){}stepEnd(t,e){}log(t,e){}warn(t,e){}error(t,e){}getElapsed(){return 0}getRemaining(){return 3e4}summary(){}getExecutionId(){return""}isDebugEnabled(){return!1}},l=class{constructor(t){this.stepStartTimes=new Map,this.stepDurations=new Map,this.TIMEOUT_MS=3e4,this.actionName=t,this.executionStartTime=Date.now();const e=Date.now(),r=Math.random().toString(36).substring(2,9);this.executionId=`exec_${e}_${r}`,this.logHeader()}logHeader(){console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution started`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`);void 0!==globalThis.litNodeContext?(console.log(`[${this.actionName}] Context: LIT Node`),console.log(`[${this.actionName}] Node: ${globalThis.litNodeContext?.nodeAddress||"unknown"}`)):console.log(`[${this.actionName}] Context: Browser/Test`),console.log(`[${this.actionName}] ========================================`)}stepStart(t,e){this.stepStartTimes.set(t,Date.now());const r=Date.now()-this.executionStartTime,i=this.TIMEOUT_MS-r;console.log(`[Step ${t}] ${e}...`),console.log(`[Step ${t}] Elapsed: ${r}ms | Remaining: ${i}ms`)}stepEnd(t,e=5e3){const r=this.stepStartTimes.get(t);if(!r)return console.warn(`[Step ${t}] \u26a0\ufe0f stepEnd called without matching stepStart`),void 0;const i=Date.now()-r;this.stepDurations.set(t,i),console.log(`[Step ${t}] Duration: ${i}ms`),i>e&&console.warn(`\u26a0\ufe0f [Step ${t}] Took ${i}ms (> ${e}ms threshold)`);const o=Date.now()-this.executionStartTime,a=this.TIMEOUT_MS-o;a<5e3&&console.warn(`\u26a0\ufe0f [Step ${t}] Time remaining: ${a}ms (approaching timeout)`)}log(t,e){console.log(`[Step ${t}] ${e}`)}warn(t,e){console.warn(`\u26a0\ufe0f [Step ${t}] ${e}`)}error(t,e){console.error(`\u274c [Step ${t}] ${e}`)}getElapsed(){return Date.now()-this.executionStartTime}getRemaining(){return this.TIMEOUT_MS-this.getElapsed()}summary(){const t=Date.now()-this.executionStartTime,e=this.TIMEOUT_MS-t;if(console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution Summary`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`),console.log(`[${this.actionName}] Total time: ${(t/1e3).toFixed(2)}s`),console.log(`[${this.actionName}] Time remaining: ${e}ms`),this.stepDurations.size>0){console.log(`[${this.actionName}] Step breakdown:`);for(const[e,r]of this.stepDurations.entries()){const i=(r/t*100).toFixed(1);console.log(`[${this.actionName}] Step ${e}: ${r}ms (${i}%)`)}}t>2e4&&console.warn(`\u26a0\ufe0f [${this.actionName}] Slow execution: ${(t/1e3).toFixed(2)}s (${e}ms remaining)`),e<5e3&&console.warn(`\u26a0\ufe0f [${this.actionName}] CRITICAL: Only ${e}ms remaining before timeout!`),console.log(`[${this.actionName}] ========================================`)}getExecutionId(){return this.executionId}isDebugEnabled(){return!0}},u=class{static create(t="LIT Action",e){return e??globalThis.debugAction??!1?new l(t):new c}},d=class{constructor(t){this.config=t,this.timeout=t.timeout||15e3,this.logger=u.create("BitcoinDataProvider")}async getCurrentBlockHeight(){if(this.logger.stepStart("0","getCurrentBlockHeight"),this.config.rpcHelper){const t=await this.config.rpcHelper.getBlockCount();return this.logger.stepEnd("0"),t}const t=new AbortController,e=setTimeout((()=>t.abort()),this.timeout);try{const r=await fetch(`${this.config.providerUrl}/blocks/tip/height`,{signal:t.signal,headers:{Accept:"text/plain","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(e),!r.ok)throw new Error(`Failed to fetch block height: ${r.status} ${r.statusText}`);const i=await r.text(),o=parseInt(i.trim(),10);if(isNaN(o))throw new Error(`Invalid block height response: ${i}`);return this.logger.stepEnd("0"),o}catch(t){if(clearTimeout(e),this.logger.stepEnd("0"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOs(t){this.logger.log("0",`Original address: "${t}"`);const e=this.stripNetworkPrefix(t);if(this.logger.log("0",`Cleaned address: "${e}"`),this.config.rpcHelper)return await this.fetchUTXOsFromRPC(e);try{return await this.fetchUTXOsFromProvider(this.config.providerUrl,e)}catch(t){if(this.logger.warn("0",`Primary provider failed: ${t.message}`),this.config.fallbackProviders&&this.config.fallbackProviders.length>0){const t=[...this.config.fallbackProviders].sort(((t,e)=>t.priority-e.priority));for(const r of t)try{return this.logger.log("0",`Trying fallback: ${r.name} (${r.url})`),await this.fetchUTXOsFromProvider(r.url,e)}catch(t){this.logger.warn("0",`Fallback ${r.name} failed: ${t.message}`);continue}}throw new Error(`Failed to fetch UTXOs: ${t.message}`)}}stripNetworkPrefix(t){return t.replace(/^(REGTEST_|TESTNET_|MAINNET_)/,"")}async fetchUTXOsFromRPC(t){if(!this.config.rpcHelper)throw new Error("RPC helper not configured");const e=this.config.rpcWallet||"";return(await this.config.rpcHelper.listUnspent(e,t)).map((t=>({txid:t.txid,vout:t.vout,satoshis:BigInt(Math.round(1e8*t.amount)),confirmations:t.confirmations})))}parseBlockstreamUTXOs(t,e){if(!Array.isArray(t))throw new Error("Invalid Blockstream response format: expected an array");return t.map((t=>{let r=0;return t.status&&"object"==typeof t.status?t.status.confirmed&&(r=void 0!==t.status.block_height&&t.status.block_height>0?e-t.status.block_height+1:1):void 0!==t.confirmations&&(r=t.confirmations),{txid:t.txid,vout:void 0!==t.vout?t.vout:t.n,satoshis:BigInt(void 0!==t.value?t.value:t.satoshis),confirmations:r}}))}async fetchUTXOsFromProvider(t,e){this.logger.stepStart("1","fetchUTXOsFromProvider");const r=`${t}/address/${e}/utxos`;this.logger.log("1",`Fetching UTXOs from ${r}`);const i=await this.getCurrentBlockHeight();this.logger.log("1",`Current block height: ${i}`);const o=new AbortController,a=setTimeout((()=>o.abort()),this.timeout);try{const t=await fetch(r,{signal:o.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(a),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();this.logger.log("1",`Raw API Response: ${JSON.stringify(e,null,2)}`);const s=this.parseBlockstreamUTXOs(e,i);return this.logger.stepEnd("1"),s}catch(t){if(clearTimeout(a),this.logger.stepEnd("1"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOSet(t,e){let r=[],i=null;for(let e=1;e<=3;e++)try{this.logger.log("2",`UTXO query attempt ${e}/3...`),r=await this.getUTXOs(t),this.logger.log("2",`UTXO query succeeded on attempt ${e}`),i=null;break}catch(t){i=t,this.logger.error("2",`UTXO query failed on attempt ${e}: ${t.message}`),e<3&&(this.logger.log("2","Waiting 500ms before retry..."),await new Promise((t=>setTimeout(t,500))))}if(i)throw new Error(`Failed to query UTXOs after 3 attempts: ${i.message}`);const o=r.reduce(((t,e)=>t+e.satoshis),0n),a=r.filter((t=>t.confirmations>=e)).reduce(((t,e)=>t+e.satoshis),0n),s=o-a;return{utxos:r,totalBalance:o,totalUTXOs:r.length,confirmedBalance:a,unconfirmedBalance:s}}async getBalance(t){const e=this.stripNetworkPrefix(t),r=`${this.config.providerUrl}/address/${e}`;this.logger.log("3",`Fetching balance from ${r}`);const i=new AbortController,o=setTimeout((()=>i.abort()),this.timeout);try{const t=await fetch(r,{signal:i.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(o),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();if(!e.chain_stats)throw new Error("Invalid response: missing chain_stats");const a=e.chain_stats.funded_txo_sum,s=e.chain_stats.spent_txo_sum;if(void 0===a||void 0===s)throw new Error(`Invalid response: missing required fields. funded_txo_sum: ${a}, spent_txo_sum: ${s}`);const n=BigInt(a-s);return this.logger.log("3",`Balance: ${n.toString()} sats`),this.logger.log("3",` - Funded: ${a} sats, Spent: ${s} sats`),n}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);const r=t.message||"Unknown error";throw this.logger.log("3",`Balance fetch failed: ${r}`),new Error(`Failed to fetch Bitcoin balance for address ${e}: ${r}`)}}async getTransaction(t){if(this.config.rpcHelper)try{const e=this.config.rpcWallet||"",r=await this.config.rpcHelper.getTransaction(e,t);return{txid:r.txid,confirmations:r.confirmations||0}}catch(t){if(t.message?.includes("Invalid or non-wallet transaction")||-5===t.code)return null;throw t}const e=new AbortController,r=setTimeout((()=>e.abort()),this.timeout);try{const i=await fetch(`${this.config.providerUrl}/tx/${t}`,{signal:e.signal});if(clearTimeout(r),!i.ok){if(404===i.status)return null;throw new Error(`Bitcoin provider error: ${i.status} ${i.statusText}`)}const o=await i.json();return o.status?{txid:o.txid,confirmations:o.status.confirmed&&o.status.block_height?1:0}:null}catch(t){if(clearTimeout(r),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);if(t.message?.includes("404"))return null;throw t}}};function h(t,e){if(0===t)return{termDurationDays:0,termLengthDays:30*e,isExpired:!1,daysUntilExpiry:30*e,daysIntoGracePeriod:0};const r=30*e,i=Math.floor(Date.now()/1e3)-t,o=Math.floor(i/86400);return{termDurationDays:o,termLengthDays:r,isExpired:o>r,daysUntilExpiry:Math.max(0,r-o),daysIntoGracePeriod:Math.max(0,o-r)}}function p(t,e){if(!t)return 13e3;const r=Math.min(e,30);return 11e3+9e3*(r*r)/900}function g(e,r,i){const o=e*r/10000000000000000n;if(0n===i)return{collateralValueUsd:o,collateralRatioBps:Number.MAX_SAFE_INTEGER};return{collateralValueUsd:o,collateralRatioBps:Number(o*t*10000n/i)}}function m(t){return t/100}var f=class{constructor(t,e,r){this.mode=r,e&&Array.isArray(e)?this.sources=e:t&&Array.isArray(t)?this.sources=this.buildSources(t):this.sources=this.buildSources(),this.sources.sort(((t,e)=>t.priority-e.priority)),this.validateProviderCount()}validateProviderCount(){if("prod"===this.mode&&this.sources.length<3)throw new Error(`Production mode requires at least 3 price providers for consensus. Currently configured: ${this.sources.length} provider(s). Supported providers: CoinGecko, Binance, Coinbase, CryptoCompare (with API key)`);if(0===this.sources.length)throw new Error("No price providers configured. At least one provider is required.")}buildSources(t){const e=[];if(!t||0===t.length)return this.getDefaultSources();let r=1;for(const i of t){switch(i.name.toLowerCase()){case"coingecko":e.push(this.createCoinGeckoSource(i.apiKey,r++));break;case"binance":e.push(this.createBinanceSource(i.apiKey,i.apiSecret,r++));break;case"coinbase":e.push(this.createCoinbaseSource(i.apiKey,r++));break;case"cryptocompare":if(!i.apiKey)throw new Error("CryptoCompare requires an API key. Please provide apiKey in priceProviders config.");e.push(this.createCryptoCompareSource(i.apiKey,r++));break;default:console.warn(`[Price Oracle] Unknown provider: ${i.name} - skipping`)}}return e}createCryptoCompareSource(t,e){return{name:"CryptoCompare",fetchPrice:async()=>{const e=new URL("https://min-api.cryptocompare.com/data/price");e.searchParams.set("fsym","BTC"),e.searchParams.set("tsyms","USDT"),e.searchParams.set("api_key",t);const r=await(async t=>{const e=new AbortController,r=setTimeout((()=>e.abort()),5e3);try{const i=await fetch(t,{signal:e.signal,headers:{Accept:"application/json"}});if(clearTimeout(r),!i.ok)throw new Error(`HTTP ${i.status} ${i.statusText}`);return await i.json()}catch(t){if(clearTimeout(r),"AbortError"===t.name)throw new Error("Request timeout after 5000ms");throw t}})(e.toString()),i=Number(r?.USDT);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid CryptoCompare price payload");const o=(await Lit.Actions.broadcastAndCollect({name:"cryptoComparePrice",value:i.toString()})).map((t=>parseFloat(t)));return o.sort(((t,e)=>t-e)),o[Math.floor(o.length/2)]},priority:e}}createFetchJson(){return async(t,e)=>{const r=new AbortController,i=setTimeout((()=>r.abort()),5e3);try{const i=await fetch(t,{headers:{Accept:"application/json",...e||{}},signal:r.signal});if(!i.ok)throw new Error(`HTTP ${i.status} ${i.statusText}`);return await i.json()}finally{clearTimeout(i)}}}getDefaultSources(){return[this.createCoinGeckoSource(void 0,1),this.createBinanceSource(void 0,void 0,2),this.createCoinbaseSource(void 0,3)]}createCoinGeckoSource(t,e=1){const r=this.createFetchJson();return{name:"CoinGecko",fetchPrice:async()=>{const t=new URL("https://api.coingecko.com/api/v3/simple/price");t.searchParams.set("ids","bitcoin"),t.searchParams.set("vs_currencies","usd");const e=await r(t.toString()),i=Number(e?.bitcoin?.usd);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid CoinGecko price payload");const o=(await Lit.Actions.broadcastAndCollect({name:"coinGeckoPrice",value:i.toString()})).map((t=>parseFloat(t)));return o.sort(((t,e)=>t-e)),o[Math.floor(o.length/2)]},priority:e}}createBinanceSource(t,e,r=2){const i=this.createFetchJson();return{name:"Binance",fetchPrice:async()=>{const e=new URL("https://api.binance.com/api/v3/ticker/price");e.searchParams.set("symbol","BTCUSDT");const r=t?{"X-MBX-APIKEY":t}:void 0,o=await i(e.toString(),r),a=Number(o?.price);if(!Number.isFinite(a)||a<=0)throw new Error("Invalid Binance price payload");const s=(await Lit.Actions.broadcastAndCollect({name:"binancePrice",value:a.toString()})).map((t=>parseFloat(t)));return s.sort(((t,e)=>t-e)),s[Math.floor(s.length/2)]},priority:r}}createCoinbaseSource(t,e=3){const r=this.createFetchJson();return{name:"Coinbase",fetchPrice:async()=>{const e=t?{"CB-ACCESS-KEY":t}:void 0,i=await r("https://api.coinbase.com/v2/prices/BTC-USD/spot",e),o=Number(i?.data?.amount);if(!Number.isFinite(o)||o<=0)throw new Error("Invalid Coinbase price payload");const a=(await Lit.Actions.broadcastAndCollect({name:"coinbasePrice",value:o.toString()})).map((t=>parseFloat(t)));return a.sort(((t,e)=>t-e)),a[Math.floor(a.length/2)]},priority:e}}async getBTCPrice(){const t=Date.now();console.log("[Price Oracle] Fetching BTC price from external sources (parallel)..."),console.log(`[Price Oracle] Start time: ${t}`);const e=new Promise(((e,r)=>{setTimeout((()=>{const e=Date.now()-t;console.error(`[Price Oracle] \u274c GLOBAL TIMEOUT after ${e}ms`),r(new Error("Price oracle global timeout after 15000ms"))}),15e3)})),r=this.sources.map((async t=>{const e=Date.now();try{console.log(`[Price Oracle] [${e}] Querying ${t.name}...`);const r=await t.fetchPrice(),i=Date.now()-e;return console.log(`[Price Oracle] \u2705 [+${i}ms] ${t.name}: $${r.toLocaleString()}`),{source:t.name,priceUSD:r}}catch(r){const i=Date.now()-e;throw console.warn(`[Price Oracle] \u26a0\ufe0f [+${i}ms] ${t.name} failed: ${r.message}`),r}}));try{console.log("[Price Oracle] Waiting for first success (race pattern)...");const i=await Promise.race([Promise.race(r.map((t=>t.catch((t=>({error:t})))))),e]);if("error"in i){console.log("[Price Oracle] First result was error, waiting for any success...");const e=Date.now(),i=new Promise(((t,r)=>{setTimeout((()=>{const t=Date.now()-e;console.error(`[Price Oracle] \u274c FALLBACK TIMEOUT after ${t}ms`),r(new Error("Price oracle fallback timeout after 10000ms"))}),1e4)})),o=await Promise.race([Promise.allSettled(r),i]),a=Date.now()-e;console.log(`[Price Oracle] Fallback completed in ${a}ms`);const s=o.find((t=>"fulfilled"===t.status));if(!s){const e=Date.now()-t;throw console.error(`[Price Oracle] All sources failed after ${e}ms`),new Error("All price sources failed")}const{source:n,priceUSD:c}=s.value,l=Date.now()-t;console.log(`[Price Oracle] Using ${n} (first successful after failures) - total time: ${l}ms`);const u=Math.round(100*c),d=1000000n*BigInt(u);return console.log(`[Price Oracle] Price with 8 decimals: ${d}`),d}const{source:o,priceUSD:a}=i,s=Date.now()-t;console.log(`[Price Oracle] Using ${o} (fastest response) - total time: ${s}ms`);const n=Math.round(100*a),c=1000000n*BigInt(n);return console.log(`[Price Oracle] Price with 8 decimals: ${c}`),c}catch(e){const r=Date.now()-t;throw console.error(`[Price Oracle] Failed after ${r}ms: ${e.message}`),new Error(`All price sources failed: ${e.message}`)}}async getBTCPriceConsensus(){console.log("[Price Oracle] Fetching BTC price with consensus...");const t=(await Promise.allSettled(this.sources.map((async t=>{const e=await t.fetchPrice();return{source:t.name,price:e}})))).filter((t=>"fulfilled"===t.status)).map((t=>t.value));if(0===t.length)throw new Error("No price sources returned data");console.log(`[Price Oracle] Got prices from ${t.length}/${this.sources.length} sources:`),t.forEach((t=>{console.log(` ${t.source}: $${t.price.toLocaleString()}`)}));const e=t.map((t=>t.price));e.sort(((t,e)=>t-e));const r=e[Math.floor(e.length/2)],i=e[0],o=e[e.length-1]/i,a=t.filter((t=>Math.abs(t.price-r)/r<=.02));if(o>1.05&&a.length===t.length)throw new Error(`Price consensus failed: sources too dispersed (${(100*(o-1)).toFixed(1)}% spread)`);if(a.length<t.length){const e=t.filter((t=>!a.find((e=>e.source===t.source))));if(console.log(`[Price Oracle] \u26a0\ufe0f Detected ${e.length} outlier(s):`),e.forEach((t=>{const e=Math.abs(t.price-r)/r;console.log(` ${t.source}: $${t.price.toLocaleString()} (${(100*e).toFixed(1)}% deviation)`)})),a.length<2)throw new Error("Price consensus failed: insufficient valid sources after outlier removal");console.log(`[Price Oracle] \u2705 Outliers filtered, continuing with ${a.length} valid sources`);const i=a.map((t=>t.price));i.sort(((t,e)=>t-e));const o=i[Math.floor(i.length/2)];console.log(`[Price Oracle] \u2705 Consensus price (median): $${o.toLocaleString()}`);const s=Math.round(100*o);return 1000000n*BigInt(s)}console.log(`[Price Oracle] \u2705 Consensus price (median): $${r.toLocaleString()}`);const s=Math.round(100*r);return 1000000n*BigInt(s)}};function w(){const t=Math.floor(Date.now()/1e3);return r=t,Math.floor(r/e)*e;var r}0,0;var y=class{constructor(t){this.config=t,this.bitcoinProvider=t.bitcoinProvider}async calculateBalance(t,e){const r=await this.bitcoinProvider.getBalance(e),i=await this.getAuthorizedSpendsFromContract(t),o=i.reduce(((t,e)=>t+e.satoshis),0n),a=r>o?r-o:0n;return{totalUTXOs:[],totalBalance:r,authorizedUTXOs:i,authorizedBalance:o,authorizedSpendsHash:this.computeAuthorizedSpendsHash(t,i),availableUTXOs:[],availableBalance:a,vaultAddress:e,positionId:t,timestamp:Date.now()}}async calculateTrustedBalance(t,e,r){if(r)return await this.calculateBalance(t,e);const i=this.config.minConfirmations||6,o=await this.bitcoinProvider.getUTXOSet(e,i),a=await this.getAuthorizedSpendsFromContract(t);for(const t of a){if(o.utxos.some((e=>e.txid===t.txid&&e.vout===t.vout)))throw new Error(`Authorized UTXO ${t.txid}:${t.vout} not yet spent. Transaction was authorized in smart contract but not signed and broadcasted to Bitcoin network. Complete the transaction by signing and broadcasting it.`)}const s=a.reduce(((t,e)=>t+e.satoshis),0n),n=o.utxos.filter((t=>!this.isUTXOAuthorized(t,a))),c=n.reduce(((t,e)=>t+e.satoshis),0n),l=this.computeAuthorizedSpendsHash(t,a);return{totalUTXOs:o.utxos,totalBalance:o.totalBalance,authorizedUTXOs:a,authorizedBalance:s,authorizedSpendsHash:l,availableUTXOs:n,availableBalance:c,vaultAddress:e,positionId:t,timestamp:Date.now()}}async getTrustedBalance(t,e){return(await this.calculateTrustedBalance(t,e)).availableBalance}async getAvailableUTXOs(t,e){return(await this.calculateTrustedBalance(t,e)).availableUTXOs}async isUTXOAvailable(t,e,r,i){return(await this.getAvailableUTXOs(t,e)).some((t=>t.txid===r&&t.vout===i))}isUTXOAuthorized(t,e){return e.some((e=>e.txid===t.txid&&e.vout===t.vout))}async getAuthorizedSpendsFromContract(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let r;r=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const i=new ethers.providers.StaticJsonRpcProvider(r,{name:"any",chainId:this.config.chainId}),o=new ethers.Contract(this.config.contractAddress,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getAuthorizedSpends",outputs:[{components:[{internalType:"string",name:"txid",type:"string"},{internalType:"uint32",name:"vout",type:"uint32"},{internalType:"uint256",name:"satoshis",type:"uint256"},{internalType:"string",name:"targetAddress",type:"string"},{internalType:"uint256",name:"targetAmount",type:"uint256"},{internalType:"uint256",name:"authorizedAt",type:"uint256"}],internalType:"struct LoanOperationsManager.AuthorizedSpend[]",name:"",type:"tuple[]"}],stateMutability:"view",type:"function"}],i);return(await o.getAuthorizedSpends(e)).map((e=>({txid:e.txid,vout:Number(e.vout),satoshis:BigInt(e.satoshis.toString()),positionId:t,targetAddress:e.targetAddress,targetAmount:BigInt(e.targetAmount.toString()),timestamp:Number(e.authorizedAt)})))}computeAuthorizedSpendsHash(t,e){const r=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;if(0===e.length)return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[r,[]]));const i=e.map((t=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.txid)),r=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.targetAddress));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","uint32","uint256","bytes32","uint256","uint256"],[e,t.vout,t.satoshis.toString(),r,t.targetAmount.toString(),t.timestamp]))}));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[r,i]))}};var T=class{constructor(t){this.termManagerAddress=t.termManagerAddress,this.loanOpsManagerAddress=t.loanOpsManagerAddress,this.chain=t.chain,this.chainId=t.chainId,this.rpcUrl=t.rpcUrl}async getLiquidationThreshold(){try{let t;t=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const e=new ethers.providers.StaticJsonRpcProvider(t,{name:"any",chainId:this.chainId}),r=[{inputs:[],name:"liquidationThreshold",outputs:[{internalType:"uint256",name:"",type:"uint256"}],stateMutability:"view",type:"function"}],i=new ethers.Contract(this.loanOpsManagerAddress,r,e),o=await i.liquidationThreshold();return Number(o.toString())}catch(t){throw console.error("[ProtocolParameters] Error fetching liquidation threshold:",t.message),new Error(`Failed to fetch liquidation threshold: ${t.message}`)}}async getTermFees(t){try{let e;e=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const r=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),i=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint88",name:"originationFee",type:"uint88"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],o=new ethers.Contract(this.termManagerAddress,i,r),a=await o.getTermFees(t);return{originationFeeBps:Number(a.originationFee?.toString?.()??a[0]?.toString?.()??a[0]),extensionFeeBps:Number(a.extensionFee?.toString?.()??a[1]?.toString?.()??a[1])}}catch(t){throw console.error("[ProtocolParameters] Error fetching term fees:",t.message),new Error(`Failed to fetch term fees: ${t.message}`)}}async getAuthorizedSpendsHash(t){try{const e=this.rpcUrl||await Lit.Actions.getRpcUrl({chain:this.chain}),r=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),i=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`,o=new ethers.Contract(this.loanOpsManagerAddress,[{inputs:[{name:"positionId",type:"bytes32"}],name:"getAuthorizedSpendsHash",outputs:[{name:"",type:"bytes32"}],stateMutability:"view",type:"function"}],r);return await o.getAuthorizedSpendsHash(i)}catch(t){throw console.error("[ProtocolParameters] Error fetching authorized spends hash:",t.message),new Error(`Failed to fetch authorized spends hash: ${t.message}`)}}};function b(t){return new T(t)}var S=class{constructor(t){this.config=t}async getVaultSnapshot(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const r=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress),i=await this.config.priceOracle.getBTCPrice(),o=b({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[a,s]=await Promise.all([o.getLiquidationThreshold(),o.getTermFees(e.selectedTerm)]),n=h(e.termStartTimestamp,e.selectedTerm),c=p(n.isExpired,n.daysIntoGracePeriod),l=g(r.availableBalance,i,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:r.totalBalance,totalUTXOs:r.totalUTXOs,authorizedSpendsSats:r.authorizedBalance,authorizedSpendsHash:r.authorizedSpendsHash,availableBTCSats:r.availableBalance,availableUTXOs:r.availableUTXOs,btcPriceUsd:i,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:n.termDurationDays,termLengthDays:n.termLengthDays,isExpired:n.isExpired,daysUntilExpiry:n.daysUntilExpiry,daysIntoGracePeriod:n.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:a,originationFeeBps:s.originationFeeBps,extensionFeeBps:s.extensionFeeBps,timestamp:Date.now()}}async getVaultSnapshotFast(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const r=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress,!0),i=await this.config.priceOracle.getBTCPrice(),o=b({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[a,s]=await Promise.all([o.getLiquidationThreshold(),o.getTermFees(e.selectedTerm)]),n=h(e.termStartTimestamp,e.selectedTerm),c=p(n.isExpired,n.daysIntoGracePeriod),l=g(r.availableBalance,i,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:r.totalBalance,totalUTXOs:r.totalUTXOs,authorizedSpendsSats:r.authorizedBalance,authorizedSpendsHash:r.authorizedSpendsHash,availableBTCSats:r.availableBalance,availableUTXOs:r.availableUTXOs,btcPriceUsd:i,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:n.termDurationDays,termLengthDays:n.termLengthDays,isExpired:n.isExpired,daysUntilExpiry:n.daysUntilExpiry,daysIntoGracePeriod:n.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:a,originationFeeBps:s.originationFeeBps,extensionFeeBps:s.extensionFeeBps,timestamp:Date.now()}}async queryPositionState(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let r;r=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const i=new ethers.providers.StaticJsonRpcProvider(r,{name:"any",chainId:this.config.chainId}),o=new ethers.Contract(this.config.contractAddress,[{inputs:[],name:"core",outputs:[{internalType:"address",name:"",type:"address"}],stateMutability:"view",type:"function"}],i),a=await o.core(),n=new ethers.Contract(a,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getPositionDetails",outputs:[{components:[{internalType:"bytes32",name:"positionId",type:"bytes32"},{internalType:"bytes32",name:"pkpId",type:"bytes32"},{internalType:"uint256",name:"ucdDebt",type:"uint256"},{internalType:"string",name:"vaultAddress",type:"string"},{internalType:"address",name:"borrower",type:"address"},{internalType:"uint40",name:"createdAt",type:"uint40"},{internalType:"uint40",name:"lastUpdated",type:"uint40"},{internalType:"uint16",name:"selectedTerm",type:"uint16"},{internalType:"uint40",name:"expiryAt",type:"uint40"},{internalType:"enum LoanStatusLib.LoanStatus",name:"status",type:"uint8"}],internalType:"struct IPositionManagerCore.Position",name:"",type:"tuple"}],stateMutability:"view",type:"function"}],i),c=await n.getPositionDetails(e),l=Number(c.status);if(l<0||l>7)throw console.error("[VaultSnapshot] INVALID STATUS - ABI decoding error detected"),console.error(" Position ID:",e),console.error(" Contract:",this.config.contractAddress),console.error(" Status decoded:",l,"(expected 0-7)"),console.error(" Full position:",JSON.stringify(c,null,2)),new Error(`Invalid position status decoded from contract: ${l} (expected 0-7). This indicates an ABI decoding issue. Position: ${e}`);const u=Number(c.expiryAt),d=Number(c.selectedTerm),h=(g=d,0===(p=u)?0:p-30*g*86400);var p,g;return{positionId:c.positionId,pkpId:c.pkpId,borrower:c.borrower,vaultAddress:this.parseVaultAddress(c.vaultAddress,s(this.config.chain)),ucdDebt:BigInt(c.ucdDebt.toString()),termStartTimestamp:h,selectedTerm:d,status:l,expiryAt:u}}parseVaultAddress(t,e){try{const r=JSON.parse(t);if("object"==typeof r&&null!==r){if("sepolia"===e.toLowerCase())return r.regtest||r.testnet||r.mainnet;const t=function(t){return o[t]}(e);return"testnet"===t&&(r.testnet||r.regtest)||r.mainnet}}catch(t){}return t}async isLiquidatable(t){return(await this.getVaultSnapshot(t)).isLiquidatable}async getCollateralRatio(t){return(await this.getVaultSnapshot(t)).collateralRatioBps}async getAvailableBalance(t){return(await this.getVaultSnapshot(t)).availableBTCSats}async hasSufficientCollateral(e,r,i){const o=await this.getVaultSnapshot(e);return function(e,r,i,o){const a=r+i;return 0n===a||Number(e*t*10000n/a)>=o}(o.collateralValueUsd,o.ucdDebt,r,i)}};var v=1000000000000n,A=100000000000000n;(async()=>{console.log("[Liquidation Validator] ========================================"),console.log("[Liquidation Validator] DEBUG: Received globalThis parameters:"),console.log("[Liquidation Validator] globalThis.contractAddresses:",globalThis.contractAddresses),console.log("[Liquidation Validator] globalThis.contractAddresses:",globalThis.contractAddresses),console.log("[Liquidation Validator] globalThis.positionId:",globalThis.positionId),console.log("[Liquidation Validator] ========================================");let t,e,i,o="0";try{o="0a",console.log("[Step 0a] Validating configuration...");const s=globalThis.chain,c=globalThis.bitcoinProviderUrl,l=globalThis.positionId;if(!s)throw new Error('Missing required parameter: "chain"');if(!c)throw new Error('Missing required parameter: "bitcoinProviderUrl"');if(!l)throw new Error('Missing required parameter: "positionId"');let u,h,p;o="0b",h=s,p="sepolia"===s?"testnet":"regtest",u={name:"Custom Provider",url:c,minConfirmations:1,network:p},o="0c";const g=globalThis.contractAddresses;if(!g||"object"!=typeof g)throw new Error("Missing or invalid 'contractAddresses' parameter");if(e=g.PositionManager,i=g.LoanOperationsManagerModule,!e||!i)throw new Error("Missing one or more required contract addresses (PositionManager, LoanOperationsManagerModule)");t=new ethers.providers.JsonRpcProvider(globalThis.rpcUrl||Lit.Actions.getRpcUrl());const T=new f({provider:u,priceProviders:globalThis.priceProviders}),b=new d({provider:u}),$=(a={contractAddress:i,chain:s,chainId:n(h),rpcUrl:globalThis.rpcUrl,bitcoinProvider:b,minConfirmations:1},new y(a)),P=function(t){return new S(t)}({contractAddress:e,termManagerAddress:g.TermManagerModule,loanOpsManagerAddress:i,chain:s,chainId:n(h),rpcUrl:globalThis.rpcUrl,vaultBalance:$,priceOracle:T});o="1",console.log("[Step 1] Getting vault snapshot...");const E=await P.getVaultSnapshot(l);if(E.btcPriceUsd<v||E.btcPriceUsd>A)throw new Error(`Bitcoin price ${E.btcPriceUsd} is outside acceptable range (1000000000000 - 100000000000000). This may indicate oracle manipulation or stale price data. Please try again later.`);console.log(` \u2705 BTC price validation passed: $${(Number(E.btcPriceUsd)/1e8).toFixed(2)}`),o="2",console.log("[Step 2] Validating position state...");if(!["ACTIVE","EXPIRED","LIQUIDATABLE"].includes(E.status))throw new Error(`Invalid position status for liquidation: ${r(E.status)}. Must be ACTIVE, EXPIRED, or LIQUIDATABLE.`);if(console.log(` \u2705 Position status valid for liquidation: ${r(E.status)}`),o="3",console.log("[Step 3] Checking liquidation eligibility..."),!E.isLiquidatable)throw new Error(`Position is not liquidatable (${m(E.collateralRatioBps)}% >= ${m(E.currentLiquidationThreshold)}%) - liquidation rejected.`);console.log(" \u2705 Position is liquidatable"),console.log(` - Current ratio: ${m(E.collateralRatioBps)}%`),console.log(` - Liquidation threshold: ${m(E.currentLiquidationThreshold)}%`),o="4",console.log("[Step 4] Building authorization message...");const U=w()+10,I=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256"],[l,U,E.btcPriceUsd.toString()]),B=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256","uint256"],[l,U,E.btcPriceUsd.toString(),E.availableBTCSats.toString()]),C=ethers.utils.keccak256(I),x=ethers.utils.keccak256(B),O=globalThis.publicKey;o="5",console.log("[Step 5] Signing authorization...");const D=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(C),publicKey:O,sigName:"liquidationAuth"}),k=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(x),publicKey:O,sigName:"liquidationBonusAuth"});console.log("[Liquidation Validator] \u2705 Complete"),Lit.Actions.setResponse({response:JSON.stringify({approved:!0,positionId:l,btcPrice:E.btcPriceUsd.toString(),btcAmountSats:E.availableBTCSats.toString(),signature:D,signatureBonus:k,timestamp:U,validatorPkp:O})})}catch(t){console.error("[Liquidation Validator] \u274c Failed:",t.message),console.error(`[Liquidation Validator] Failed at step: ${o}`),Lit.Actions.setResponse({response:JSON.stringify({approved:!1,reason:t.message||t.toString(),failedStep:o,positionId:globalThis.positionId,timestamp:Date.now()})})}var a})()})();
1
+ var _LIT_ACTION_=(()=>{var t=1000000000000000000n,e=100;function r(t){switch(t){case 0:return"PENDING_DEPOSIT";case 1:return"PENDING_MINT";case 2:return"ACTIVE";case 3:return"EXPIRED";case 4:return"LIQUIDATABLE";case 5:return"LIQUIDATED";case 6:return"REPAID";case 7:return"CLOSED";default:return`UNKNOWN(${t})`}}var i=(t=>(t.SEPOLIA="sepolia",t.ETHEREUM="ethereum",t.HARDHAT="hardhat",t))(i||{}),o={sepolia:"testnet",ethereum:"mainnet",hardhat:"regtest"},a={ethereum:1,sepolia:11155111,hardhat:31337};function s(t){const e=t.toLowerCase().trim();if("sepolia"===e)return"sepolia";if("ethereum"===e||"mainnet"===e)return"ethereum";if("hardhat"===e)return"hardhat";throw new Error(`Unsupported EVM chain: "${t}". Supported chains: ${Object.values(i).join(", ")}`)}function n(t){return a[t]}var c=class{stepStart(t,e){}stepEnd(t,e){}log(t,e){}warn(t,e){}error(t,e){}getElapsed(){return 0}getRemaining(){return 3e4}summary(){}getExecutionId(){return""}isDebugEnabled(){return!1}},l=class{constructor(t){this.stepStartTimes=new Map,this.stepDurations=new Map,this.TIMEOUT_MS=3e4,this.actionName=t,this.executionStartTime=Date.now();const e=Date.now(),r=Math.random().toString(36).substring(2,9);this.executionId=`exec_${e}_${r}`,this.logHeader()}logHeader(){console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution started`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`);void 0!==globalThis.litNodeContext?(console.log(`[${this.actionName}] Context: LIT Node`),console.log(`[${this.actionName}] Node: ${globalThis.litNodeContext?.nodeAddress||"unknown"}`)):console.log(`[${this.actionName}] Context: Browser/Test`),console.log(`[${this.actionName}] ========================================`)}stepStart(t,e){this.stepStartTimes.set(t,Date.now());const r=Date.now()-this.executionStartTime,i=this.TIMEOUT_MS-r;console.log(`[Step ${t}] ${e}...`),console.log(`[Step ${t}] Elapsed: ${r}ms | Remaining: ${i}ms`)}stepEnd(t,e=5e3){const r=this.stepStartTimes.get(t);if(!r)return console.warn(`[Step ${t}] \u26a0\ufe0f stepEnd called without matching stepStart`),void 0;const i=Date.now()-r;this.stepDurations.set(t,i),console.log(`[Step ${t}] Duration: ${i}ms`),i>e&&console.warn(`\u26a0\ufe0f [Step ${t}] Took ${i}ms (> ${e}ms threshold)`);const o=Date.now()-this.executionStartTime,a=this.TIMEOUT_MS-o;a<5e3&&console.warn(`\u26a0\ufe0f [Step ${t}] Time remaining: ${a}ms (approaching timeout)`)}log(t,e){console.log(`[Step ${t}] ${e}`)}warn(t,e){console.warn(`\u26a0\ufe0f [Step ${t}] ${e}`)}error(t,e){console.error(`\u274c [Step ${t}] ${e}`)}getElapsed(){return Date.now()-this.executionStartTime}getRemaining(){return this.TIMEOUT_MS-this.getElapsed()}summary(){const t=Date.now()-this.executionStartTime,e=this.TIMEOUT_MS-t;if(console.log(`[${this.actionName}] ========================================`),console.log(`[${this.actionName}] Execution Summary`),console.log(`[${this.actionName}] Execution ID: ${this.executionId}`),console.log(`[${this.actionName}] Total time: ${(t/1e3).toFixed(2)}s`),console.log(`[${this.actionName}] Time remaining: ${e}ms`),this.stepDurations.size>0){console.log(`[${this.actionName}] Step breakdown:`);for(const[e,r]of this.stepDurations.entries()){const i=(r/t*100).toFixed(1);console.log(`[${this.actionName}] Step ${e}: ${r}ms (${i}%)`)}}t>2e4&&console.warn(`\u26a0\ufe0f [${this.actionName}] Slow execution: ${(t/1e3).toFixed(2)}s (${e}ms remaining)`),e<5e3&&console.warn(`\u26a0\ufe0f [${this.actionName}] CRITICAL: Only ${e}ms remaining before timeout!`),console.log(`[${this.actionName}] ========================================`)}getExecutionId(){return this.executionId}isDebugEnabled(){return!0}},u=class{static create(t="LIT Action",e){return e??globalThis.debugAction??!1?new l(t):new c}},d=class{constructor(t){this.config=t,this.timeout=t.timeout||15e3,this.logger=u.create("BitcoinDataProvider")}async getCurrentBlockHeight(){if(this.logger.stepStart("0","getCurrentBlockHeight"),this.config.rpcHelper){const t=await this.config.rpcHelper.getBlockCount();return this.logger.stepEnd("0"),t}const t=new AbortController,e=setTimeout((()=>t.abort()),this.timeout);try{const r=await fetch(`${this.config.providerUrl}/blocks/tip/height`,{signal:t.signal,headers:{Accept:"text/plain","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(e),!r.ok)throw new Error(`Failed to fetch block height: ${r.status} ${r.statusText}`);const i=await r.text(),o=parseInt(i.trim(),10);if(isNaN(o))throw new Error(`Invalid block height response: ${i}`);return this.logger.stepEnd("0"),o}catch(t){if(clearTimeout(e),this.logger.stepEnd("0"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOs(t){this.logger.log("0",`Original address: "${t}"`);const e=this.stripNetworkPrefix(t);if(this.logger.log("0",`Cleaned address: "${e}"`),this.config.rpcHelper)return await this.fetchUTXOsFromRPC(e);try{return await this.fetchUTXOsFromProvider(this.config.providerUrl,e)}catch(t){if(this.logger.warn("0",`Primary provider failed: ${t.message}`),this.config.fallbackProviders&&this.config.fallbackProviders.length>0){const t=[...this.config.fallbackProviders].sort(((t,e)=>t.priority-e.priority));for(const r of t)try{return this.logger.log("0",`Trying fallback: ${r.name} (${r.url})`),await this.fetchUTXOsFromProvider(r.url,e)}catch(t){this.logger.warn("0",`Fallback ${r.name} failed: ${t.message}`);continue}}throw new Error(`Failed to fetch UTXOs: ${t.message}`)}}stripNetworkPrefix(t){return t.replace(/^(REGTEST_|TESTNET_|MAINNET_)/,"")}async fetchUTXOsFromRPC(t){if(!this.config.rpcHelper)throw new Error("RPC helper not configured");const e=this.config.rpcWallet||"";return(await this.config.rpcHelper.listUnspent(e,t)).map((t=>({txid:t.txid,vout:t.vout,satoshis:BigInt(Math.round(1e8*t.amount)),confirmations:t.confirmations})))}parseBlockstreamUTXOs(t,e){if(!Array.isArray(t))throw new Error("Invalid Blockstream response format: expected an array");return t.map((t=>{let r=0;return t.status&&"object"==typeof t.status?t.status.confirmed&&(r=void 0!==t.status.block_height&&t.status.block_height>0?e-t.status.block_height+1:1):void 0!==t.confirmations&&(r=t.confirmations),{txid:t.txid,vout:void 0!==t.vout?t.vout:t.n,satoshis:BigInt(void 0!==t.value?t.value:t.satoshis),confirmations:r}}))}async fetchUTXOsFromProvider(t,e){this.logger.stepStart("1","fetchUTXOsFromProvider");const r=`${t}/address/${e}/utxos`;this.logger.log("1",`Fetching UTXOs from ${r}`);const i=await this.getCurrentBlockHeight();this.logger.log("1",`Current block height: ${i}`);const o=new AbortController,a=setTimeout((()=>o.abort()),this.timeout);try{const t=await fetch(r,{signal:o.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(a),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();this.logger.log("1",`Raw API Response: ${JSON.stringify(e,null,2)}`);const s=this.parseBlockstreamUTXOs(e,i);return this.logger.stepEnd("1"),s}catch(t){if(clearTimeout(a),this.logger.stepEnd("1"),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);throw t}}async getUTXOSet(t,e){let r=[],i=null;for(let e=1;e<=3;e++)try{this.logger.log("2",`UTXO query attempt ${e}/3...`),r=await this.getUTXOs(t),this.logger.log("2",`UTXO query succeeded on attempt ${e}`),i=null;break}catch(t){i=t,this.logger.error("2",`UTXO query failed on attempt ${e}: ${t.message}`),e<3&&(this.logger.log("2","Waiting 500ms before retry..."),await new Promise((t=>setTimeout(t,500))))}if(i)throw new Error(`Failed to query UTXOs after 3 attempts: ${i.message}`);const o=r.reduce(((t,e)=>t+e.satoshis),0n),a=r.filter((t=>t.confirmations>=e)).reduce(((t,e)=>t+e.satoshis),0n),s=o-a;return{utxos:r,totalBalance:o,totalUTXOs:r.length,confirmedBalance:a,unconfirmedBalance:s}}async getBalance(t){const e=this.stripNetworkPrefix(t),r=`${this.config.providerUrl}/address/${e}`;this.logger.log("3",`Fetching balance from ${r}`);const i=new AbortController,o=setTimeout((()=>i.abort()),this.timeout);try{const t=await fetch(r,{signal:i.signal,headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":"Mozilla/5.0 (compatible; DiamondHandsValidator/1.0)","ngrok-skip-browser-warning":"true"}});if(clearTimeout(o),!t.ok)throw new Error(`Bitcoin provider error: ${t.status} ${t.statusText}`);const e=await t.json();if(!e.chain_stats)throw new Error("Invalid response: missing chain_stats");const a=e.chain_stats.funded_txo_sum,s=e.chain_stats.spent_txo_sum;if(void 0===a||void 0===s)throw new Error(`Invalid response: missing required fields. funded_txo_sum: ${a}, spent_txo_sum: ${s}`);const n=BigInt(a-s);return this.logger.log("3",`Balance: ${n.toString()} sats`),this.logger.log("3",` - Funded: ${a} sats, Spent: ${s} sats`),n}catch(t){if(clearTimeout(o),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);const r=t.message||"Unknown error";throw this.logger.log("3",`Balance fetch failed: ${r}`),new Error(`Failed to fetch Bitcoin balance for address ${e}: ${r}`)}}async getTransaction(t){if(this.config.rpcHelper)try{const e=this.config.rpcWallet||"",r=await this.config.rpcHelper.getTransaction(e,t);return{txid:r.txid,confirmations:r.confirmations||0}}catch(t){if(t.message?.includes("Invalid or non-wallet transaction")||-5===t.code)return null;throw t}const e=new AbortController,r=setTimeout((()=>e.abort()),this.timeout);try{const i=await fetch(`${this.config.providerUrl}/tx/${t}`,{signal:e.signal});if(clearTimeout(r),!i.ok){if(404===i.status)return null;throw new Error(`Bitcoin provider error: ${i.status} ${i.statusText}`)}const o=await i.json();return o.status?{txid:o.txid,confirmations:o.status.confirmed&&o.status.block_height?1:0}:null}catch(t){if(clearTimeout(r),"AbortError"===t.name)throw new Error(`Request timeout after ${this.timeout}ms`);if(t.message?.includes("404"))return null;throw t}}};function h(t,e){if(0===t)return{termDurationDays:0,termLengthDays:30*e,isExpired:!1,daysUntilExpiry:30*e,daysIntoGracePeriod:0};const r=30*e,i=Math.floor(Date.now()/1e3)-t,o=Math.floor(i/86400);return{termDurationDays:o,termLengthDays:r,isExpired:o>r,daysUntilExpiry:Math.max(0,r-o),daysIntoGracePeriod:Math.max(0,o-r)}}function p(t,e){if(!t)return 13e3;const r=Math.min(e,30);return 11e3+9e3*(r*r)/900}function g(e,r,i){const o=e*r/10000000000000000n;if(0n===i)return{collateralValueUsd:o,collateralRatioBps:Number.MAX_SAFE_INTEGER};return{collateralValueUsd:o,collateralRatioBps:Number(o*t*10000n/i)}}function m(t){return t/100}var f=class{constructor(t,e,r){this.mode=r,e&&Array.isArray(e)?this.sources=e:t&&Array.isArray(t)?this.sources=this.buildSources(t):this.sources=this.buildSources(),this.sources.sort(((t,e)=>t.priority-e.priority)),this.validateProviderCount()}validateProviderCount(){if("prod"===this.mode&&this.sources.length<3)throw new Error(`Production mode requires at least 3 price providers for consensus. Currently configured: ${this.sources.length} provider(s). Supported providers: CoinGecko, Binance, Coinbase, CryptoCompare (with API key)`);if(0===this.sources.length)throw new Error("No price providers configured. At least one provider is required.")}buildSources(t){const e=[];if(!t||0===t.length)return this.getDefaultSources();let r=1;for(const i of t){switch(i.name.toLowerCase()){case"coingecko":e.push(this.createCoinGeckoSource(i.apiKey,r++));break;case"binance":e.push(this.createBinanceSource(i.apiKey,i.apiSecret,r++));break;case"coinbase":e.push(this.createCoinbaseSource(i.apiKey,r++));break;case"cryptocompare":if(!i.apiKey)throw new Error("CryptoCompare requires an API key. Please provide apiKey in priceProviders config.");e.push(this.createCryptoCompareSource(i.apiKey,r++));break;default:console.warn(`[Price Oracle] Unknown provider: ${i.name} - skipping`)}}return e}createCryptoCompareSource(t,e){return{name:"CryptoCompare",fetchPrice:async()=>{const e=new URL("https://min-api.cryptocompare.com/data/price");e.searchParams.set("fsym","BTC"),e.searchParams.set("tsyms","USDT"),e.searchParams.set("api_key",t);const r=await(async t=>{const e=new AbortController,r=setTimeout((()=>e.abort()),5e3);try{const i=await fetch(t,{signal:e.signal,headers:{Accept:"application/json"}});if(clearTimeout(r),!i.ok)throw new Error(`HTTP ${i.status} ${i.statusText}`);return await i.json()}catch(t){if(clearTimeout(r),"AbortError"===t.name)throw new Error("Request timeout after 5000ms");throw t}})(e.toString()),i=Number(r?.USDT);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid CryptoCompare price payload");const o=(await Lit.Actions.broadcastAndCollect({name:"cryptoComparePrice",value:i.toString()})).map((t=>parseFloat(t)));return o.sort(((t,e)=>t-e)),o[Math.floor(o.length/2)]},priority:e}}createFetchJson(){return async(t,e)=>{const r=new AbortController,i=setTimeout((()=>r.abort()),5e3);try{const i=await fetch(t,{headers:{Accept:"application/json",...e||{}},signal:r.signal});if(!i.ok)throw new Error(`HTTP ${i.status} ${i.statusText}`);return await i.json()}finally{clearTimeout(i)}}}getDefaultSources(){return[this.createCoinGeckoSource(void 0,1),this.createBinanceSource(void 0,void 0,2),this.createCoinbaseSource(void 0,3)]}createCoinGeckoSource(t,e=1){const r=this.createFetchJson();return{name:"CoinGecko",fetchPrice:async()=>{const t=new URL("https://api.coingecko.com/api/v3/simple/price");t.searchParams.set("ids","bitcoin"),t.searchParams.set("vs_currencies","usd");const e=await r(t.toString()),i=Number(e?.bitcoin?.usd);if(!Number.isFinite(i)||i<=0)throw new Error("Invalid CoinGecko price payload");const o=(await Lit.Actions.broadcastAndCollect({name:"coinGeckoPrice",value:i.toString()})).map((t=>parseFloat(t)));return o.sort(((t,e)=>t-e)),o[Math.floor(o.length/2)]},priority:e}}createBinanceSource(t,e,r=2){const i=this.createFetchJson();return{name:"Binance",fetchPrice:async()=>{const e=new URL("https://api.binance.com/api/v3/ticker/price");e.searchParams.set("symbol","BTCUSDT");const r=t?{"X-MBX-APIKEY":t}:void 0,o=await i(e.toString(),r),a=Number(o?.price);if(!Number.isFinite(a)||a<=0)throw new Error("Invalid Binance price payload");const s=(await Lit.Actions.broadcastAndCollect({name:"binancePrice",value:a.toString()})).map((t=>parseFloat(t)));return s.sort(((t,e)=>t-e)),s[Math.floor(s.length/2)]},priority:r}}createCoinbaseSource(t,e=3){const r=this.createFetchJson();return{name:"Coinbase",fetchPrice:async()=>{const e=t?{"CB-ACCESS-KEY":t}:void 0,i=await r("https://api.coinbase.com/v2/prices/BTC-USD/spot",e),o=Number(i?.data?.amount);if(!Number.isFinite(o)||o<=0)throw new Error("Invalid Coinbase price payload");const a=(await Lit.Actions.broadcastAndCollect({name:"coinbasePrice",value:o.toString()})).map((t=>parseFloat(t)));return a.sort(((t,e)=>t-e)),a[Math.floor(a.length/2)]},priority:e}}async getBTCPrice(){const t=Date.now();console.log("[Price Oracle] Fetching BTC price from external sources (parallel)..."),console.log(`[Price Oracle] Start time: ${t}`);const e=new Promise(((e,r)=>{setTimeout((()=>{const e=Date.now()-t;console.error(`[Price Oracle] \u274c GLOBAL TIMEOUT after ${e}ms`),r(new Error("Price oracle global timeout after 15000ms"))}),15e3)})),r=this.sources.map((async t=>{const e=Date.now();try{console.log(`[Price Oracle] [${e}] Querying ${t.name}...`);const r=await t.fetchPrice(),i=Date.now()-e;return console.log(`[Price Oracle] \u2705 [+${i}ms] ${t.name}: $${r.toLocaleString()}`),{source:t.name,priceUSD:r}}catch(r){const i=Date.now()-e;throw console.warn(`[Price Oracle] \u26a0\ufe0f [+${i}ms] ${t.name} failed: ${r.message}`),r}}));try{console.log("[Price Oracle] Waiting for first success (race pattern)...");const i=await Promise.race([Promise.race(r.map((t=>t.catch((t=>({error:t})))))),e]);if("error"in i){console.log("[Price Oracle] First result was error, waiting for any success...");const e=Date.now(),i=new Promise(((t,r)=>{setTimeout((()=>{const t=Date.now()-e;console.error(`[Price Oracle] \u274c FALLBACK TIMEOUT after ${t}ms`),r(new Error("Price oracle fallback timeout after 10000ms"))}),1e4)})),o=await Promise.race([Promise.allSettled(r),i]),a=Date.now()-e;console.log(`[Price Oracle] Fallback completed in ${a}ms`);const s=o.find((t=>"fulfilled"===t.status));if(!s){const e=Date.now()-t;throw console.error(`[Price Oracle] All sources failed after ${e}ms`),new Error("All price sources failed")}const{source:n,priceUSD:c}=s.value,l=Date.now()-t;console.log(`[Price Oracle] Using ${n} (first successful after failures) - total time: ${l}ms`);const u=Math.round(100*c),d=1000000n*BigInt(u);return console.log(`[Price Oracle] Price with 8 decimals: ${d}`),d}const{source:o,priceUSD:a}=i,s=Date.now()-t;console.log(`[Price Oracle] Using ${o} (fastest response) - total time: ${s}ms`);const n=Math.round(100*a),c=1000000n*BigInt(n);return console.log(`[Price Oracle] Price with 8 decimals: ${c}`),c}catch(e){const r=Date.now()-t;throw console.error(`[Price Oracle] Failed after ${r}ms: ${e.message}`),new Error(`All price sources failed: ${e.message}`)}}async getBTCPriceConsensus(){console.log("[Price Oracle] Fetching BTC price with consensus...");const t=(await Promise.allSettled(this.sources.map((async t=>{const e=await t.fetchPrice();return{source:t.name,price:e}})))).filter((t=>"fulfilled"===t.status)).map((t=>t.value));if(0===t.length)throw new Error("No price sources returned data");console.log(`[Price Oracle] Got prices from ${t.length}/${this.sources.length} sources:`),t.forEach((t=>{console.log(` ${t.source}: $${t.price.toLocaleString()}`)}));const e=t.map((t=>t.price));e.sort(((t,e)=>t-e));const r=e[Math.floor(e.length/2)],i=e[0],o=e[e.length-1]/i,a=t.filter((t=>Math.abs(t.price-r)/r<=.02));if(o>1.05&&a.length===t.length)throw new Error(`Price consensus failed: sources too dispersed (${(100*(o-1)).toFixed(1)}% spread)`);if(a.length<t.length){const e=t.filter((t=>!a.find((e=>e.source===t.source))));if(console.log(`[Price Oracle] \u26a0\ufe0f Detected ${e.length} outlier(s):`),e.forEach((t=>{const e=Math.abs(t.price-r)/r;console.log(` ${t.source}: $${t.price.toLocaleString()} (${(100*e).toFixed(1)}% deviation)`)})),a.length<2)throw new Error("Price consensus failed: insufficient valid sources after outlier removal");console.log(`[Price Oracle] \u2705 Outliers filtered, continuing with ${a.length} valid sources`);const i=a.map((t=>t.price));i.sort(((t,e)=>t-e));const o=i[Math.floor(i.length/2)];console.log(`[Price Oracle] \u2705 Consensus price (median): $${o.toLocaleString()}`);const s=Math.round(100*o);return 1000000n*BigInt(s)}console.log(`[Price Oracle] \u2705 Consensus price (median): $${r.toLocaleString()}`);const s=Math.round(100*r);return 1000000n*BigInt(s)}};function w(){const t=Math.floor(Date.now()/1e3);return r=t,Math.floor(r/e)*e;var r}0,0;var y=class{constructor(t){this.config=t,this.bitcoinProvider=t.bitcoinProvider}async calculateBalance(t,e){const r=await this.bitcoinProvider.getBalance(e),i=await this.getAuthorizedSpendsFromContract(t),o=i.reduce(((t,e)=>t+e.satoshis),0n),a=r>o?r-o:0n;return{totalUTXOs:[],totalBalance:r,authorizedUTXOs:i,authorizedBalance:o,authorizedSpendsHash:this.computeAuthorizedSpendsHash(t,i),availableUTXOs:[],availableBalance:a,vaultAddress:e,positionId:t,timestamp:Date.now()}}async calculateTrustedBalance(t,e,r){if(r)return await this.calculateBalance(t,e);const i=this.config.minConfirmations||6,o=await this.bitcoinProvider.getUTXOSet(e,i),a=await this.getAuthorizedSpendsFromContract(t);for(const t of a){if(o.utxos.some((e=>e.txid===t.txid&&e.vout===t.vout)))throw new Error(`Authorized UTXO ${t.txid}:${t.vout} not yet spent. Transaction was authorized in smart contract but not signed and broadcasted to Bitcoin network. Complete the transaction by signing and broadcasting it.`)}const s=a.reduce(((t,e)=>t+e.satoshis),0n),n=o.utxos.filter((t=>!this.isUTXOAuthorized(t,a))),c=n.reduce(((t,e)=>t+e.satoshis),0n),l=this.computeAuthorizedSpendsHash(t,a);return{totalUTXOs:o.utxos,totalBalance:o.totalBalance,authorizedUTXOs:a,authorizedBalance:s,authorizedSpendsHash:l,availableUTXOs:n,availableBalance:c,vaultAddress:e,positionId:t,timestamp:Date.now()}}async getTrustedBalance(t,e){return(await this.calculateTrustedBalance(t,e)).availableBalance}async getAvailableUTXOs(t,e){return(await this.calculateTrustedBalance(t,e)).availableUTXOs}async isUTXOAvailable(t,e,r,i){return(await this.getAvailableUTXOs(t,e)).some((t=>t.txid===r&&t.vout===i))}isUTXOAuthorized(t,e){return e.some((e=>e.txid===t.txid&&e.vout===t.vout))}async getAuthorizedSpendsFromContract(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let r;r=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const i=new ethers.providers.StaticJsonRpcProvider(r,this.config.chainId),o=new ethers.Contract(this.config.contractAddress,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getAuthorizedSpends",outputs:[{components:[{internalType:"string",name:"txid",type:"string"},{internalType:"uint32",name:"vout",type:"uint32"},{internalType:"uint256",name:"satoshis",type:"uint256"},{internalType:"string",name:"targetAddress",type:"string"},{internalType:"uint256",name:"targetAmount",type:"uint256"},{internalType:"uint256",name:"authorizedAt",type:"uint256"}],internalType:"struct LoanOperationsManager.AuthorizedSpend[]",name:"",type:"tuple[]"}],stateMutability:"view",type:"function"}],i);return(await o.getAuthorizedSpends(e)).map((e=>({txid:e.txid,vout:Number(e.vout),satoshis:BigInt(e.satoshis.toString()),positionId:t,targetAddress:e.targetAddress,targetAmount:BigInt(e.targetAmount.toString()),timestamp:Number(e.authorizedAt)})))}computeAuthorizedSpendsHash(t,e){const r=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;if(0===e.length)return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[r,[]]));const i=e.map((t=>{const e=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.txid)),r=ethers.utils.keccak256(ethers.utils.toUtf8Bytes(t.targetAddress));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","uint32","uint256","bytes32","uint256","uint256"],[e,t.vout,t.satoshis.toString(),r,t.targetAmount.toString(),t.timestamp]))}));return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["bytes32","bytes32[]"],[r,i]))}};var T=class{constructor(t){this.termManagerAddress=t.termManagerAddress,this.loanOpsManagerAddress=t.loanOpsManagerAddress,this.chain=t.chain,this.chainId=t.chainId,this.rpcUrl=t.rpcUrl}async getLiquidationThreshold(){try{let t;t=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const e=new ethers.providers.StaticJsonRpcProvider(t,{name:"any",chainId:this.chainId}),r=[{inputs:[],name:"liquidationThreshold",outputs:[{internalType:"uint256",name:"",type:"uint256"}],stateMutability:"view",type:"function"}],i=new ethers.Contract(this.loanOpsManagerAddress,r,e),o=await i.liquidationThreshold();return Number(o.toString())}catch(t){throw console.error("[ProtocolParameters] Error fetching liquidation threshold:",t.message),new Error(`Failed to fetch liquidation threshold: ${t.message}`)}}async getTermFees(t){try{let e;e=this.rpcUrl?this.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.chain});const r=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),i=[{inputs:[{internalType:"uint256",name:"_termMonths",type:"uint256"}],name:"getTermFees",outputs:[{internalType:"uint88",name:"originationFee",type:"uint88"},{internalType:"uint88",name:"extensionFee",type:"uint88"}],stateMutability:"view",type:"function"}],o=new ethers.Contract(this.termManagerAddress,i,r),a=await o.getTermFees(t);return{originationFeeBps:Number(a.originationFee?.toString?.()??a[0]?.toString?.()??a[0]),extensionFeeBps:Number(a.extensionFee?.toString?.()??a[1]?.toString?.()??a[1])}}catch(t){throw console.error("[ProtocolParameters] Error fetching term fees:",t.message),new Error(`Failed to fetch term fees: ${t.message}`)}}async getAuthorizedSpendsHash(t){try{const e=this.rpcUrl||await Lit.Actions.getRpcUrl({chain:this.chain}),r=new ethers.providers.StaticJsonRpcProvider(e,{name:"any",chainId:this.chainId}),i=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`,o=new ethers.Contract(this.loanOpsManagerAddress,[{inputs:[{name:"positionId",type:"bytes32"}],name:"getAuthorizedSpendsHash",outputs:[{name:"",type:"bytes32"}],stateMutability:"view",type:"function"}],r);return await o.getAuthorizedSpendsHash(i)}catch(t){throw console.error("[ProtocolParameters] Error fetching authorized spends hash:",t.message),new Error(`Failed to fetch authorized spends hash: ${t.message}`)}}};function b(t){return new T(t)}var S=class{constructor(t){this.config=t}async getVaultSnapshot(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const r=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress),i=await this.config.priceOracle.getBTCPrice(),o=b({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[a,s]=await Promise.all([o.getLiquidationThreshold(),o.getTermFees(e.selectedTerm)]),n=h(e.termStartTimestamp,e.selectedTerm),c=p(n.isExpired,n.daysIntoGracePeriod),l=g(r.availableBalance,i,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:r.totalBalance,totalUTXOs:r.totalUTXOs,authorizedSpendsSats:r.authorizedBalance,authorizedSpendsHash:r.authorizedSpendsHash,availableBTCSats:r.availableBalance,availableUTXOs:r.availableUTXOs,btcPriceUsd:i,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:n.termDurationDays,termLengthDays:n.termLengthDays,isExpired:n.isExpired,daysUntilExpiry:n.daysUntilExpiry,daysIntoGracePeriod:n.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:a,originationFeeBps:s.originationFeeBps,extensionFeeBps:s.extensionFeeBps,timestamp:Date.now()}}async getVaultSnapshotFast(t){const e=await this.queryPositionState(t);console.log(`[Vault Snapshot] Raw vault address from contract: "${e.vaultAddress}"`);const r=await this.config.vaultBalance.calculateTrustedBalance(t,e.vaultAddress,!0),i=await this.config.priceOracle.getBTCPrice(),o=b({termManagerAddress:this.config.termManagerAddress,loanOpsManagerAddress:this.config.loanOpsManagerAddress,chain:this.config.chain,chainId:this.config.chainId||1,rpcUrl:this.config.rpcUrl}),[a,s]=await Promise.all([o.getLiquidationThreshold(),o.getTermFees(e.selectedTerm)]),n=h(e.termStartTimestamp,e.selectedTerm),c=p(n.isExpired,n.daysIntoGracePeriod),l=g(r.availableBalance,i,e.ucdDebt),u=l.collateralRatioBps<c,d=l.collateralRatioBps-c;return{positionId:e.positionId,pkpId:e.pkpId,borrower:e.borrower,vaultAddress:e.vaultAddress,ucdDebt:e.ucdDebt,termStartTimestamp:e.termStartTimestamp,selectedTerm:e.selectedTerm,status:e.status,expiryAt:e.expiryAt,totalBTCSats:r.totalBalance,totalUTXOs:r.totalUTXOs,authorizedSpendsSats:r.authorizedBalance,authorizedSpendsHash:r.authorizedSpendsHash,availableBTCSats:r.availableBalance,availableUTXOs:r.availableUTXOs,btcPriceUsd:i,collateralValueUsd:l.collateralValueUsd,collateralRatioBps:l.collateralRatioBps,termDurationDays:n.termDurationDays,termLengthDays:n.termLengthDays,isExpired:n.isExpired,daysUntilExpiry:n.daysUntilExpiry,daysIntoGracePeriod:n.daysIntoGracePeriod,currentLiquidationThreshold:c,isLiquidatable:u,marginToLiquidationBps:d,liquidationThresholdBps:a,originationFeeBps:s.originationFeeBps,extensionFeeBps:s.extensionFeeBps,timestamp:Date.now()}}async queryPositionState(t){const e=t.startsWith("0x")?t:`0x${t.padStart(64,"0")}`;let r;r=this.config.rpcUrl?this.config.rpcUrl:await Lit.Actions.getRpcUrl({chain:this.config.chain});const i=new ethers.providers.StaticJsonRpcProvider(r,this.config.chainId),o=new ethers.Contract(this.config.contractAddress,[{inputs:[],name:"core",outputs:[{internalType:"address",name:"",type:"address"}],stateMutability:"view",type:"function"}],i),a=await o.core(),n=new ethers.Contract(a,[{inputs:[{internalType:"bytes32",name:"positionId",type:"bytes32"}],name:"getPositionDetails",outputs:[{components:[{internalType:"bytes32",name:"positionId",type:"bytes32"},{internalType:"bytes32",name:"pkpId",type:"bytes32"},{internalType:"uint256",name:"ucdDebt",type:"uint256"},{internalType:"string",name:"vaultAddress",type:"string"},{internalType:"address",name:"borrower",type:"address"},{internalType:"uint40",name:"createdAt",type:"uint40"},{internalType:"uint40",name:"lastUpdated",type:"uint40"},{internalType:"uint16",name:"selectedTerm",type:"uint16"},{internalType:"uint40",name:"expiryAt",type:"uint40"},{internalType:"enum LoanStatusLib.LoanStatus",name:"status",type:"uint8"}],internalType:"struct IPositionManagerCore.Position",name:"",type:"tuple"}],stateMutability:"view",type:"function"}],i),c=await n.getPositionDetails(e),l=Number(c.status);if(l<0||l>7)throw console.error("[VaultSnapshot] INVALID STATUS - ABI decoding error detected"),console.error(" Position ID:",e),console.error(" Contract:",this.config.contractAddress),console.error(" Status decoded:",l,"(expected 0-7)"),console.error(" Full position:",JSON.stringify(c,null,2)),new Error(`Invalid position status decoded from contract: ${l} (expected 0-7). This indicates an ABI decoding issue. Position: ${e}`);const u=Number(c.expiryAt),d=Number(c.selectedTerm),h=(g=d,0===(p=u)?0:p-30*g*86400);var p,g;return{positionId:c.positionId,pkpId:c.pkpId,borrower:c.borrower,vaultAddress:this.parseVaultAddress(c.vaultAddress,s(this.config.chain)),ucdDebt:BigInt(c.ucdDebt.toString()),termStartTimestamp:h,selectedTerm:d,status:l,expiryAt:u}}parseVaultAddress(t,e){try{const r=JSON.parse(t);if("object"==typeof r&&null!==r){if("sepolia"===e.toLowerCase())return r.regtest||r.testnet||r.mainnet;const t=function(t){return o[t]}(e);return"testnet"===t&&(r.testnet||r.regtest)||r.mainnet}}catch(t){}return t}async isLiquidatable(t){return(await this.getVaultSnapshot(t)).isLiquidatable}async getCollateralRatio(t){return(await this.getVaultSnapshot(t)).collateralRatioBps}async getAvailableBalance(t){return(await this.getVaultSnapshot(t)).availableBTCSats}async hasSufficientCollateral(e,r,i){const o=await this.getVaultSnapshot(e);return function(e,r,i,o){const a=r+i;return 0n===a||Number(e*t*10000n/a)>=o}(o.collateralValueUsd,o.ucdDebt,r,i)}};var v=1000000000000n,A=100000000000000n;(async()=>{console.log("[Liquidation Validator] ========================================"),console.log("[Liquidation Validator] DEBUG: Received globalThis parameters:"),console.log("[Liquidation Validator] globalThis.contractAddresses:",globalThis.contractAddresses),console.log("[Liquidation Validator] globalThis.contractAddresses:",globalThis.contractAddresses),console.log("[Liquidation Validator] globalThis.positionId:",globalThis.positionId),console.log("[Liquidation Validator] ========================================");let t,e,i,o="0";try{o="0a",console.log("[Step 0a] Validating configuration...");const s=globalThis.chain,c=globalThis.bitcoinProviderUrl,l=globalThis.positionId;if(!s)throw new Error('Missing required parameter: "chain"');if(!c)throw new Error('Missing required parameter: "bitcoinProviderUrl"');if(!l)throw new Error('Missing required parameter: "positionId"');let u,h,p;o="0b",h=s,p="sepolia"===s?"testnet":"regtest",u={name:"Custom Provider",url:c,minConfirmations:1,network:p},o="0c";const g=globalThis.contractAddresses;if(!g||"object"!=typeof g)throw new Error("Missing or invalid 'contractAddresses' parameter");if(e=g.PositionManager,i=g.LoanOperationsManagerModule,!e||!i)throw new Error("Missing one or more required contract addresses (PositionManager, LoanOperationsManagerModule)");t=new ethers.providers.JsonRpcProvider(globalThis.rpcUrl||Lit.Actions.getRpcUrl());const T=new f({provider:u,priceProviders:globalThis.priceProviders}),b=new d({provider:u}),$=(a={contractAddress:i,chain:s,chainId:n(h),rpcUrl:globalThis.rpcUrl,bitcoinProvider:b,minConfirmations:1},new y(a)),P=function(t){return new S(t)}({contractAddress:e,termManagerAddress:g.TermManagerModule,loanOpsManagerAddress:i,chain:s,chainId:n(h),rpcUrl:globalThis.rpcUrl,vaultBalance:$,priceOracle:T});o="1",console.log("[Step 1] Getting vault snapshot...");const E=await P.getVaultSnapshot(l);if(E.btcPriceUsd<v||E.btcPriceUsd>A)throw new Error(`Bitcoin price ${E.btcPriceUsd} is outside acceptable range (1000000000000 - 100000000000000). This may indicate oracle manipulation or stale price data. Please try again later.`);console.log(` \u2705 BTC price validation passed: $${(Number(E.btcPriceUsd)/1e8).toFixed(2)}`),o="2",console.log("[Step 2] Validating position state...");if(!["ACTIVE","EXPIRED","LIQUIDATABLE"].includes(E.status))throw new Error(`Invalid position status for liquidation: ${r(E.status)}. Must be ACTIVE, EXPIRED, or LIQUIDATABLE.`);if(console.log(` \u2705 Position status valid for liquidation: ${r(E.status)}`),o="3",console.log("[Step 3] Checking liquidation eligibility..."),!E.isLiquidatable)throw new Error(`Position is not liquidatable (${m(E.collateralRatioBps)}% >= ${m(E.currentLiquidationThreshold)}%) - liquidation rejected.`);console.log(" \u2705 Position is liquidatable"),console.log(` - Current ratio: ${m(E.collateralRatioBps)}%`),console.log(` - Liquidation threshold: ${m(E.currentLiquidationThreshold)}%`),o="4",console.log("[Step 4] Building authorization message...");const U=w()+10,I=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256"],[l,U,E.btcPriceUsd.toString()]),B=ethers.utils.defaultAbiCoder.encode(["bytes32","uint256","uint256","uint256"],[l,U,E.btcPriceUsd.toString(),E.availableBTCSats.toString()]),C=ethers.utils.keccak256(I),x=ethers.utils.keccak256(B),O=globalThis.publicKey;o="5",console.log("[Step 5] Signing authorization...");const D=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(C),publicKey:O,sigName:"liquidationAuth"}),k=await Lit.Actions.signEcdsa({toSign:ethers.utils.arrayify(x),publicKey:O,sigName:"liquidationBonusAuth"});console.log("[Liquidation Validator] \u2705 Complete"),Lit.Actions.setResponse({response:JSON.stringify({approved:!0,positionId:l,btcPrice:E.btcPriceUsd.toString(),btcAmountSats:E.availableBTCSats.toString(),signature:D,signatureBonus:k,timestamp:U,validatorPkp:O})})}catch(t){console.error("[Liquidation Validator] \u274c Failed:",t.message),console.error(`[Liquidation Validator] Failed at step: ${o}`),Lit.Actions.setResponse({response:JSON.stringify({approved:!1,reason:t.message||t.toString(),failedStep:o,positionId:globalThis.positionId,timestamp:Date.now()})})}var a})()})();
@@ -1 +1 @@
1
- 1836dc3de51790e761756806e50686604df87d9c4edcb1c6f2d8ca01c649cead
1
+ 86d79018f11b651ae8660c643a1a79111a7e51d498385ea1aff851f444ab9fff
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "actionName": "liquidation-validator",
3
- "originalSize": 84398,
4
- "minifiedSize": 35221,
5
- "compressionRatio": 0.5826796843527098,
6
- "hash": "1836dc3de51790e761756806e50686604df87d9c4edcb1c6f2d8ca01c649cead",
7
- "buildTime": 1768406363221,
3
+ "originalSize": 84350,
4
+ "minifiedSize": 35179,
5
+ "compressionRatio": 0.58294013040901,
6
+ "hash": "86d79018f11b651ae8660c643a1a79111a7e51d498385ea1aff851f444ab9fff",
7
+ "buildTime": 1768804918120,
8
8
  "version": "0.1.0"
9
9
  }