@jdr377/mapapp-agent-connector 0.1.0-pre.13 → 0.1.0-pre.15

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.
@@ -0,0 +1,17 @@
1
+ // @bun
2
+ import{AgentMediaError,DEFAULT_MESSAGE_PLAINTEXT_MAX_BYTES,MAPAPP_CATEGORY_TOKENS,MAPAPP_MESSAGE_BOX_NAME,MAPAPP_MESSAGE_ID_MAX_BYTES,MAPAPP_MESSAGE_VERSION,MESSAGEBOX_KEY_ID,MESSAGEBOX_PROTOCOL,MESSAGING_IDENTITY_MUTATION_VERSION,POST_GEOHASH_PATTERN,assertCanonicalIsoTimestamp,assertCompressedIdentityKey,assertMapAppMessageV1,assertMessagingIdentityV1,conversationIdForIdentityKeys,createAgentLockExecutor,createAgentWalletConnectionInspector,inspectAgentMedia,inspectDirectBsvEvidence,recoverNativeActionEvidence,serializeMapAppMessageV1,serializeMessagingIdentityMutationV1,trustedHTTPEndpoint,validAgentMediaInput,validateDirectBsvInput}from"./chunk-mb8a3fs5.js";import{executePaidPostAction,normalizePaidPostActionOutcome,recoverPaidPostEvidence}from"./chunk-e6y2yzbj.js";import{__callDispose,__require,__using}from"./chunk-btwmey1h.js";import{readFile,stat,writeFile}from"fs/promises";import{join as join2,resolve as resolve2}from"path";import{createNodeWallet}from"@1sat/wallet-node";import{OneSatServices}from"@1sat/wallet";import{createContext,resolveBapId as resolveBapId2,computeBapId,deriveDepositAddresses,syncAddresses}from"@1sat/actions";import{PrivateKey as PrivateKey2}from"@bsv/sdk";import*as z2 from"zod/v4";import{HTTPWalletJSON}from"@bsv/sdk";function createParticipantHTTPWallet(endpoint,originator,applicationOrigin){const baseUrl=trustedHTTPEndpoint(endpoint);try{const app=new URL(applicationOrigin);if(app.protocol!=="https:"||app.hostname!==originator||app.username||app.password||app.origin!==applicationOrigin)throw new Error}catch{throw new Error("participant_wallet_origin_invalid")}const request=Object.assign(async(resource,init)=>{const url=new URL(typeof resource==="string"?resource:resource instanceof URL?resource.href:resource.url);const selected=new URL(baseUrl);if(url.origin!==selected.origin||!url.pathname.startsWith(selected.pathname.replace(/\/$/u,"")+"/")||url.username||url.password||url.search||url.hash)throw new Error("participant_wallet_target_invalid");const method=url.pathname.slice(url.pathname.lastIndexOf("/")+1);const noPrompt=["isAuthenticated","getNetwork","getVersion"].includes(method)||typeof init?.body==="string"&&JSON.parse(init.body).seekPermission===false;const response=await fetch(url,{...init,redirect:"error",...noPrompt?{signal:AbortSignal.timeout(15000)}:{}});if(!response.body)throw new Error("participant_wallet_response_invalid");const reader=response.body.getReader(),chunks=[];let size=0;try{while(true){const{done,value}=await reader.read();if(done)break;size+=value.length;if(size>4*1024*1024){await reader.cancel();throw new Error("participant_wallet_response_too_large")}chunks.push(value)}}finally{reader.releaseLock()}const bytes=Buffer.concat(chunks);if(!response.ok){let failure={};try{const parsed=JSON.parse(bytes.toString());if(parsed&&typeof parsed==="object"&&!Array.isArray(parsed))failure=parsed}catch{}const providerCode=typeof failure.code==="string"?failure.code:"";const code=["ERR_PERMISSION_DENIED","PERMISSION_DENIED"].includes(providerCode)||typeof failure.error==="string"&&failure.error.startsWith("permission denied for ")?"ERR_PERMISSION_DENIED":["ERR_WALLET_LOCKED","WALLET_LOCKED"].includes(providerCode)||response.status===503&&failure.error==="Wallet is locked"?"ERR_WALLET_LOCKED":response.status===404?"ERR_METHOD_NOT_SUPPORTED":"ERR_WALLET_HTTP";const classified=failure.code==="ORIGIN_REFUSED"?"ERR_WALLET_ORIGIN_REFUSED":response.status===401||failure.code==="NOT_AUTHENTICATED"?"ERR_WALLET_UNAUTHENTICATED":code;throw Object.assign(new Error("participant_wallet_request_failed"),{code:classified})}return new Response(bytes,{status:response.status,headers:response.headers})},{preconnect:fetch.preconnect});return new HTTPWalletJSON(applicationOrigin,baseUrl,request)}import{createAuthProof}from"@bsv/auth";var MAPAPP_AUTH_PROTOCOL=[2,"mapapp transaction intake"];var MAPAPP_AUTH_ACTIONS={create:"mapapp create transaction intent",claim:"mapapp claim wallet invocation",evidence:"mapapp submit transaction evidence",outcome:"mapapp record wallet outcome",terminate:"mapapp terminate unused transaction intent",status:"mapapp read transaction intent status",sponsorReserve:"mapapp reserve sponsored wallet action",sponsorFund:"mapapp fund sponsored wallet action",sponsorStatus:"mapapp read sponsored wallet action",thumbnailIssue:"mapapp issue thumbnail upload capability",thumbnailStage:"mapapp stage intent thumbnail",retainedMediaStage:"mapapp stage retained media"};function createTransactionIntakeClient(options){const fetcher=options.fetch??globalThis.fetch;const baseUrl=(options.baseUrl??"/api/transaction-intakes").replace(/\/$/,"");const requestTimeoutMs=options.requestTimeoutMs??1e4;let cachedConfig=null;let configRequest=null;async function readAuthConfig(){const now=Date.now();if(cachedConfig&&cachedConfig.expiresAt>now)return cachedConfig.value;if(!configRequest){const pending=(async()=>{const configResponse=await fetchWithDeadline(fetcher,`${baseUrl}/auth-config`,{headers:{accept:"application/json"}},requestTimeoutMs);const config=await readJson(configResponse);cachedConfig={value:config,expiresAt:Date.now()+Math.min(30000,Math.max(1000,config.proofLifetimeMs/4))};return config})().finally(()=>{if(configRequest===pending)configRequest=null});configRequest=pending}return configRequest}async function request(operation,body){const config=await readAuthConfig();requireEnabledConfig(config,operation);const rawBody=new TextEncoder().encode(JSON.stringify(body));const proof=await createAuthProof({wallet:options.wallet,counterparty:config.verifierIdentityKey,action:MAPAPP_AUTH_ACTIONS[operation],body:rawBody,protocol:MAPAPP_AUTH_PROTOCOL,windowMs:config.proofLifetimeMs});const response=await fetchWithDeadline(fetcher,`${baseUrl}/${operation}`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json","x-mapapp-brc138-proof":encodeProof(proof)},body:rawBody},requestTimeoutMs);const result=await readJson(response);if(!response.ok){if(response.status===401||response.status===403){cachedConfig=null;configRequest=null}throw new TransactionIntakeClientError(response.status,result.code??"transaction_intake_request_failed",result.message??"Transaction intake request failed.")}return result}return{async createIntent(clientIntentId,intentId,actionKind="post",actionTarget){return request("create",{version:1,intentId,clientIntentId,actionKind,...actionTarget?{actionTarget}:{}})},async claimInvocation(intentId,tabId){return request("claim",{version:1,intentId,tabId})},async submitEvidence(intentId,subjectTxid,atomicBeef,profileControl,profileAliasOutpoint){return request("evidence",{version:1,intentId,subjectTxid,atomicBeefBase64:encodeBytes(atomicBeef),...profileControl?{profileControl}:{},...profileAliasOutpoint?{profileAliasOutpoint}:{}})},async recordOutcome(intentId,state,subjectTxid){await request("outcome",{version:1,intentId,state,...subjectTxid?{subjectTxid}:{}})},async terminateIntent(intentId,code){await request("terminate",{version:1,intentId,code})},async statusIntent(intentId){const result=await request("status",{version:1,intentId});return result.intake},async authenticatedRequest(operation,body,path){let target=path;if(/^https?:\/\//u.test(baseUrl)){const configured=new URL(baseUrl),selected=new URL(path,configured);if(selected.origin!==configured.origin)throw new TransactionIntakeClientError(400,"wallet_request_origin_mismatch","Authenticated requests must use the configured MapApp origin.");target=selected.toString()}else if(!path.startsWith("/api/")||path.startsWith("//")){throw new TransactionIntakeClientError(400,"wallet_request_origin_mismatch","Authenticated requests must use a same-origin MapApp API path.")}const config=await readAuthConfig();requireEnabledConfig(config,operation);const rawBody=new TextEncoder().encode(JSON.stringify(body));const proof=await createAuthProof({wallet:options.wallet,counterparty:config.verifierIdentityKey,action:MAPAPP_AUTH_ACTIONS[operation],body:rawBody,protocol:MAPAPP_AUTH_PROTOCOL,windowMs:config.proofLifetimeMs});const response=await fetchWithDeadline(fetcher,target,{method:"POST",headers:{"content-type":"application/json",accept:"application/json","x-mapapp-brc138-proof":encodeProof(proof)},body:rawBody},requestTimeoutMs);const result=await readJson(response);if(!response.ok){if(response.status===401||response.status===403){cachedConfig=null;configRequest=null}throw new TransactionIntakeClientError(response.status,result.code??"sponsor_request_failed",result.message??"Sponsor request failed.")}return result},async stageThumbnail(intentId,mediaSlot,sourceSha256,bytes){await this.authenticatedRequest("thumbnailStage",{version:1,intentId,mediaSlot,sourceSha256:sourceSha256.toLowerCase(),thumbnailBase64:encodeBytes(bytes)},`/api/media/thumbnail-stages/${encodeURIComponent(intentId)}`)},async stageRetainedMedia(intentId,media){return this.authenticatedRequest("retainedMediaStage",{version:1,intentId,media},"/api/media/freeposts")}}}class TransactionIntakeClientError extends Error{status;code;constructor(status,code,message){super(message);this.status=status;this.code=code;this.name="TransactionIntakeClientError"}}function requireEnabledConfig(config,operation){if(!config.enabled||!config.verifierIdentityKey){throw new TransactionIntakeClientError(503,"wallet_request_proof_disabled",`Transaction intake is disabled: ${config.disabledReasons?.join(", ")||"verifier unavailable"}.`)}if(JSON.stringify(config.protocol)!==JSON.stringify(MAPAPP_AUTH_PROTOCOL)||config.actions?.[operation]!==MAPAPP_AUTH_ACTIONS[operation]){throw new TransactionIntakeClientError(503,"wallet_request_proof_contract_mismatch","Transaction intake authentication does not match the pinned MapApp contract.")}}async function readJson(response){try{return await response.json()}catch{throw new TransactionIntakeClientError(response.status,"transaction_intake_invalid_response","Transaction intake returned an invalid response.")}}function encodeProof(proof){return encodeBytes(new TextEncoder().encode(JSON.stringify(proof)),true)}function encodeBytes(bytes,urlSafe=false){let binary="";for(let offset=0;offset<bytes.length;offset+=32768){binary+=String.fromCharCode(...bytes.subarray(offset,offset+32768))}const base64=btoa(binary);return urlSafe?base64.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""):base64}async function fetchWithDeadline(fetcher,resource,init,timeoutMs){const controller=new AbortController;const timer=setTimeout(()=>controller.abort(new Error("Transaction intake request timed out.")),timeoutMs);try{return await fetcher(resource,{...init,signal:controller.signal})}catch(cause){if(controller.signal.aborted){throw new TransactionIntakeClientError(504,"transaction_intake_timeout","Transaction intake request timed out.")}throw cause}finally{clearTimeout(timer)}}var U12_SPONSOR_POLICY="mapapp-sponsored-v2";var U12_RESERVATION_TTL_MS=2*60*1000;var U12_CONTRIBUTION_WINDOW_MS=24*60*60*1000;var U12_BAP_IP_WINDOW_MS=7*24*60*60*1000;function canonicalSponsoredDraft(draft){assertSponsoredDraft(draft);const value=draft.kind==="post"?{category:draft.category,geohash:draft.geohash,kind:draft.kind,...draft.mediaSha256?{mediaSha256:draft.mediaSha256.toLowerCase()}:{},policy:U12_SPONSOR_POLICY,text:draft.text}:{kind:draft.kind,...draft.mediaSha256?{mediaSha256:draft.mediaSha256.toLowerCase()}:{},parentTxid:draft.parentTxid.toLowerCase(),policy:U12_SPONSOR_POLICY,text:draft.text};return JSON.stringify(value)}function assertSponsoredDraft(draft){if(draft.kind!=="post"&&draft.kind!=="comment")throw new Error("Unsupported sponsored contribution kind.");if(!draft.text.trim())throw new Error("Sponsored contribution text is required.");if(new TextEncoder().encode(draft.text).byteLength>280)throw new Error("Sponsored contribution text exceeds 280 UTF-8 bytes.");if(draft.mediaSha256!==undefined&&!/^[0-9a-f]{64}$/i.test(draft.mediaSha256)){throw new Error("Sponsored media hash must be SHA-256 hex.")}if(draft.kind==="post"){if(!POST_GEOHASH_PATTERN.test(draft.geohash))throw new Error("Sponsored post geohash is invalid.");if(!draft.category.trim())throw new Error("Sponsored post category is required.")}else if(!/^[0-9a-f]{64}$/i.test(draft.parentTxid)){throw new Error("Sponsored reply parent txid is invalid.")}}async function solveSponsoredPow(input){for(let attempt=0;attempt<1e6;attempt+=1){const value={...input,nonce:attempt.toString(36)};const digest=new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(["mapapp-freepost-pow-v1",value.payloadHash,value.challenge.challenge,value.challenge.recentBlockHash,value.challenge.timestampWindow,String(value.challenge.difficultyBits),value.nonce].join(`
3
+ `))));if(leadingZeroBits(digest)>=value.challenge.difficultyBits)return value}throw new Error("Unable to solve sponsor proof-of-work.")}function leadingZeroBits(bytes){let total=0;for(const byte of bytes){if(byte===0){total+=8;continue}for(let bit=7;bit>=0;bit-=1){if((byte&1<<bit)===0)total+=1;else return total}}return total}async function sponsoredDraftHash(value){return[...new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(value)))].map((byte)=>byte.toString(16).padStart(2,"0")).join("")}var schema="mapapp-agent-identity-operation-v1";var uuid=(value)=>/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/u.test(value);var marker=(id)=>`agent-identity:${id}`;var reservationRejections={sponsor_bap_ip_weekly_limit:409,sponsor_global_budget_exhausted:409,sponsor_contract_disabled:503,sponsor_ip_policy_unconfigured:503,sponsor_client_ip_unavailable:503};function createAgentIdentityExecutor(options){const binding=Object.freeze({...options.binding}),deployment=new URL(options.deployment).origin;if(!options.store.updateDraft||!options.store.persistEvidence)throw new Error("identity_retention_required");const inspector=createAgentWalletConnectionInspector({wallet:options.wallet,binding});let busy=false;const blocked=(reason)=>({kind:"blocked",reason});async function exclusive(work){if(busy)return blocked("execution_in_progress");busy=true;try{return await work()}finally{busy=false}}function prepared(record){const draft=record.draft;if(draft?.schema!==schema)return null;if(!uuid(draft.operationId)||record.reference.intentId!==marker(draft.operationId)||draft.deployment!==deployment||JSON.stringify(draft.connection)!==JSON.stringify(binding)||!["prepared","attempt_started"].includes(draft.phase))throw new Error("identity_binding_changed");return draft}async function admission(caller){const inspected=await inspector.inspect(caller);if(inspected.state!=="ready")return inspected.reason;return null}const save=(draft)=>options.store.updateDraft(marker(draft.operationId),draft);const missingUnfundedReservation=(cause,draft,record)=>cause instanceof TransactionIntakeClientError&&cause.status===404&&cause.code==="sponsor_reservation_not_found"&&!draft.reservationId&&!draft.fundingArgs&&!record.returnedEvidence;const unfunded=(draft,reason)=>({kind:"recovery_required",operationId:draft.operationId,reason,retryAllowed:false,next:"execute_original_identity"});async function observation(bapId){try{const observed=await options.observed(bapId);return{publicIdentityObserved:observed,publicIdentityObservation:observed?"observed":"not_observed"}}catch{return{publicIdentityObserved:false,publicIdentityObservation:"unavailable"}}}async function reservation(draft){const{reservation:current}=await options.client.authenticatedRequest("sponsorStatus",{version:2,intentId:draft.operationId},`${deployment}/api/freeposting/u12/status`);if(!current||!uuid(current.reservationId)||current.intentId!==draft.operationId||current.kind!=="bap"||current.bapId!==draft.bapId||draft.reservationId&&current.reservationId!==draft.reservationId||!["reserved","funding","submitted","outcome_unknown","failed","expired"].includes(current.status))throw new Error("identity_reservation_changed");return current}async function recover(draft,record){if(draft.phase==="prepared")return blocked("operation_not_started");try{const current=await reservation(draft);let evidence=record.returnedEvidence;if(!evidence&&current.tx){const returned=normalizePaidPostActionOutcome("agent-wallet-node",current);if(returned.kind!=="creation_evidence"||returned.anomaly)return blocked("identity_evidence_invalid");await options.store.persistEvidence(marker(draft.operationId),returned.txid,returned.atomicBeef);evidence={txid:returned.txid,atomicBeef:Array.from(returned.atomicBeef),originator:binding.originator}}if(!evidence)return current.status==="reserved"&&!current.txid&&!draft.fundingArgs?unfunded(draft,"identity_funding_not_started"):{kind:"recovery_required",operationId:draft.operationId,reason:"original_sponsor_evidence_unavailable",sponsorState:current.status};const exact=normalizePaidPostActionOutcome("agent-wallet-node",{txid:evidence.txid,tx:evidence.atomicBeef});if(evidence.originator!==binding.originator||exact.kind!=="creation_evidence"||exact.anomaly||current.tx&&(current.txid!==evidence.txid||JSON.stringify(current.tx)!==JSON.stringify(evidence.atomicBeef)))return blocked("identity_evidence_conflict");if(!draft.authorInternalized){if(!draft.fundingArgs)return blocked("identity_filing_data_missing");const args=draft.fundingArgs;await options.wallet.internalizeAction({tx:[...evidence.atomicBeef],description:args.description,labels:args.labels,outputs:(args.outputs??[]).map((output,outputIndex)=>({outputIndex,protocol:"basket insertion",insertionRemittance:{basket:output.basket??"default",tags:output.tags??[],customInstructions:output.customInstructions}}))},binding.originator);await save({...draft,reservationId:current.reservationId,authorInternalized:true})}const local=await options.published(),observed=await observation(draft.bapId);return{kind:"reconciled",operationId:draft.operationId,bapId:draft.bapId,txid:evidence.txid,sponsorState:current.status,authorInternalized:local===draft.bapId,...observed,retryAllowed:false,next:local===draft.bapId&&observed.publicIdentityObserved?"check_readiness":"check_original_identity_status"}}catch(cause){if(missingUnfundedReservation(cause,draft,record))return unfunded(draft,"identity_reservation_required");return{kind:"recovery_required",operationId:draft.operationId,reason:"original_identity_unavailable",retryAllowed:false}}}return{prepare(caller,operationId){return exclusive(async()=>{if(!uuid(operationId))return blocked("invalid_operation_id");const denied=await admission(caller);if(denied)return blocked(denied);const records=await options.store.list(),drafts=records.map(prepared);const existing=drafts.find((draft)=>draft?.operationId===operationId);if(existing?.phase==="attempt_started")return blocked("operation_already_started");if(drafts.some((draft)=>draft?.phase==="attempt_started"))return blocked("original_identity_attempt_unresolved");if(await options.published())return blocked("identity_already_published");if(!await options.ready().catch(()=>false))return blocked("identity_sponsor_unavailable");if(!existing){if(records.some((record)=>record.reference.clientIntentId===operationId))return blocked("operation_input_conflict");const bapId=await options.bapId();const deniedAgain=await admission(caller);if(deniedAgain)return blocked(deniedAgain);await options.store.persistReference({intentId:marker(operationId),clientIntentId:operationId,nonce:`agent-identity:${operationId}`,correlationTag:`intent:agent-identity:${operationId}`},{schema,operationId,connection:binding,deployment,bapId,phase:"prepared"})}return{kind:"prepared",operationId,bapId:existing?.bapId??await options.bapId(),payer:"mapapp",authorSats:0,sponsorCost:"unknown"}})},execute(caller,operationId,signal){return exclusive(async()=>{const denied=await admission(caller);if(denied)return blocked(denied);const records=await options.store.list(),record=records.find((record2)=>prepared(record2)?.operationId===operationId);if(!record)return blocked("operation_not_prepared");const draft=prepared(record);let current;if(draft.phase==="attempt_started"){if(draft.fundingArgs||record.returnedEvidence)return recover(draft,record);try{current=await reservation(draft)}catch(cause){if(!missingUnfundedReservation(cause,draft,record))return{kind:"recovery_required",operationId,reason:"original_identity_unavailable",retryAllowed:false}}if(current&&(current.status!=="reserved"||current.txid||current.tx))return recover(draft,record)}if(records.some((value)=>{const other=prepared(value);return other?.operationId!==operationId&&other?.phase==="attempt_started"}))return blocked("original_identity_attempt_unresolved");const changed=await admission(caller);if(changed)return blocked(changed);if(signal?.aborted)return blocked("request_cancelled");if(await options.published())return blocked("identity_already_published");if(await options.bapId()!==draft.bapId||!await options.ready().catch(()=>false))return blocked("identity_prerequisite_changed");let durable={...draft,phase:"attempt_started"};await save(durable);try{if(!current){const hash=await sponsoredDraftHash(`mapapp-sponsored-bap-v2
4
+ ${draft.bapId}`);const pow=await solveSponsoredPow({challenge:await options.challenge(hash),payloadHash:hash});if(signal?.aborted||await admission(caller))throw new Error("identity_binding_changed");try{({reservation:current}=await options.client.authenticatedRequest("sponsorReserve",{version:2,intentId:operationId,kind:"bap",bapId:draft.bapId,pow},`${deployment}/api/freeposting/u12/reserve`))}catch(cause){if(cause instanceof TransactionIntakeClientError&&Object.hasOwn(reservationRejections,cause.code)&&reservationRejections[cause.code]===cause.status){await save({...durable,reservationFailureCode:cause.code});return{kind:"blocked",operationId,reason:cause.code,retryAllowed:false,next:"execute_original_identity_after_policy_resolution"}}throw cause}}if(!current||!uuid(current.reservationId)||current.intentId!==operationId||current.kind!=="bap"||current.bapId!==draft.bapId||current.status!=="reserved")throw new Error("identity_reservation_invalid");const selected=current;durable={...durable,reservationId:selected.reservationId,reservationFailureCode:undefined};await save(durable);let fundingCalled=false;await options.invoke({fund:async(args)=>{if(fundingCalled||signal?.aborted||await admission(caller))throw new Error("identity_funding_refused");fundingCalled=true;durable={...durable,fundingArgs:structuredClone(args)};await save(durable);const returned=await options.client.authenticatedRequest("sponsorFund",{version:2,reservationId:selected.reservationId,args},`${deployment}/api/freeposting/u12/fund`);const exact=normalizePaidPostActionOutcome("agent-wallet-node",returned);if(exact.kind!=="creation_evidence"||exact.anomaly)throw new Error("identity_evidence_invalid");await options.store.persistEvidence(marker(operationId),exact.txid,exact.atomicBeef);return returned}});if(await options.published()===draft.bapId){durable={...durable,authorInternalized:true};await save(durable)}const saved=(await options.store.list(marker(operationId))).find((value)=>value.reference.intentId===marker(operationId));return recover(durable,saved)}catch{return{kind:"recovery_required",operationId,reason:"original_identity_interrupted",retryAllowed:false}}})},status(caller,operationId){return exclusive(async()=>{const inspected=await inspector.inspect(caller);if(inspected.state!=="ready")return blocked(inspected.reason);const record=(await options.store.list(marker(operationId))).find((record2)=>prepared(record2)?.operationId===operationId);if(!record)return blocked("operation_not_prepared");const draft=prepared(record);return{kind:"status",operationId,phase:draft.phase,bapId:draft.bapId,...draft.reservationFailureCode&&Object.hasOwn(reservationRejections,draft.reservationFailureCode)?{sponsorRejection:draft.reservationFailureCode,next:"execute_original_identity_after_policy_resolution"}:{},authorInternalized:await options.published()===draft.bapId,...await observation(draft.bapId),retryAllowed:false}})},reconcile(caller,operationId){return exclusive(async()=>{const denied=await admission(caller);if(denied)return blocked(denied);const record=(await options.store.list(marker(operationId))).find((record2)=>prepared(record2)?.operationId===operationId);return record?recover(prepared(record),record):blocked("operation_not_prepared")})}}}import*as z from"zod/v4";var participantConnectionSchema=z.object({connectionId:z.string().min(1).max(128).regex(/^[a-zA-Z0-9.:_-]+$/u),callerId:z.string().min(1).max(128).regex(/^[a-zA-Z0-9.:_-]+$/u),originator:z.string().min(1).max(128).regex(/^[a-zA-Z0-9.-]+$/u)});function supportedParticipantBinding(binding){return binding.route==="agent_environment"&&binding.declaredAuthority==="full_root_authority"||binding.route==="existing_brc100"&&binding.declaredAuthority==="provider"}function participantAuthorityName(binding){return binding.route==="existing_brc100"?"provider":"full_root_authority"}function participantProfileMatches(binding,profile){return supportedParticipantBinding(binding)&&(binding.route==="existing_brc100"?profile.transport==="brc100_http"&&profile.authority==="provider"&&profile.qualification==="participant_release"&&profile.connectionReceipt==="mapapp-705.32":profile.transport==="direct_wallet_node"&&profile.authority==="full_root_authority"&&(profile.qualification==="participant_release"&&profile.connectionReceipt==="mapapp-705.31"||["controlled_fixture","operator_authorized_proof"].includes(profile.qualification)&&profile.connectionReceipt==="mapapp-705.21"))}function createAgentPaidPostExecutor(options){const binding=Object.freeze({...options.binding});const profile=Object.freeze({...options.proofProfile});const deployment=new URL(options.deployment).origin;if(!participantProfileMatches(binding,profile)||!options.store.updateDraft||!options.store.persistEvidence)throw new Error("agent_executor_configuration_invalid");const inspector=createAgentWalletConnectionInspector({wallet:options.wallet,binding});let busy=false;const blocked=(reason)=>({kind:"blocked",reason});const marker2=(id)=>`agent-prepared:${id}`;async function records(id,evidence=false){const retained=await options.store.list(marker2(id));const draft=retained.map(prepared).find((value)=>value?.operationId===id);return evidence&&draft?.phase==="attempt_started"?[...retained,...await options.store.list(draft.intakeId)]:retained}function prepared(record){const draft=record.draft;if(draft?.schema!=="mapapp-agent-paid-root-operation-v1")return null;if(record.reference.intentId!==marker2(draft.operationId)||!uuid2(draft.intakeId)||!sameBinding(draft.connection,binding)||draft.deployment!==deployment||!["prepared","attempt_started"].includes(draft.phase))throw new Error("agent_operation_binding_invalid");return draft}async function admission(caller,draft){const state=await inspector.inspect(caller);if(state.state!=="ready")return state.reason;const gates=await options.gates().catch(()=>null);if(!gates)return"prerequisite_unavailable";if(!gates.stackReady||!gates.intakeReady||!gates.paidRootEnabled||!gates.identityReady)return"prerequisite_blocked";if(draft){const policy=resolveFreshness(gates,draft.input.freshnessDuration);if(!policy)return"publication_policy_unavailable";if(draft.input.freshnessLockSats!==policy.lockSats||draft.input.freshnessUnlockHeight<=policy.tipHeight||draft.preparedAtHeight!==undefined&&draft.input.freshnessUnlockHeight-draft.preparedAtHeight!==policy.durationBlocks)return"publication_policy_changed"}return gates}async function exclusive(work){if(busy)return blocked("execution_in_progress");busy=true;try{return await work()}finally{busy=false}}async function reconcilePrepared(draft,currentRecords){if(draft.phase==="prepared")return blocked("operation_not_started");const matches=currentRecords.filter((record)=>record.reference.intentId===draft.intakeId);if(matches.length>1)return{kind:"recovery_required",operationId:draft.operationId,reason:"reference_conflict"};let evidence=matches[0]?.returnedEvidence;try{if(!evidence&&matches[0]){const recovered=await recoverPaidPostEvidence({wallet:options.wallet,correlationTag:matches[0].reference.correlationTag,evidenceFormat:"mapapp_post_v2"});if(recovered.kind==="recovered"){await options.store.persistEvidence(draft.intakeId,recovered.txid,recovered.atomicBeef);evidence={txid:recovered.txid,atomicBeef:Array.from(recovered.atomicBeef),originator:binding.originator}}}if(evidence){if(evidence.originator!==binding.originator)return blocked("evidence_originator_changed");await options.client.submitEvidence(draft.intakeId,evidence.txid,new Uint8Array(evidence.atomicBeef))}return{kind:"reconciled",operationId:draft.operationId,intake:await options.client.statusIntent(draft.intakeId),evidenceResubmitted:!!evidence}}catch{return{kind:"recovery_required",operationId:draft.operationId,reason:"original_intake_unavailable"}}}return{prepare(caller,request){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(!uuid2(request.operationId)||!validInput(request.input))return blocked("invalid_text_post_input");const existing=(await records(request.operationId)).map(prepared).find((draft2)=>draft2?.operationId===request.operationId);if(existing){const requested=canonicalInput({...request.input,freshnessDuration:request.input.freshnessDuration??existing.input.freshnessDuration});if(JSON.stringify(canonicalInput(existing.input))!==JSON.stringify(requested))return blocked("operation_input_conflict");if(existing.phase!=="prepared")return blocked("operation_already_started");const gates2=await admission(caller,existing);if(typeof gates2==="string")return blocked(gates2);try{const media2=existing.input.media?(await inspectAgentMedia(existing.input.media)).summary:undefined;return preparation(existing,media2)}catch(error){return blocked(error instanceof AgentMediaError?error.code:"media_inspection_unavailable")}}const gates=await admission(caller);if(typeof gates==="string")return blocked(gates);const freshness=resolveFreshness(gates,request.input.freshnessDuration??gates.freshnessDefaultDuration);if(!freshness)return blocked("publication_policy_unavailable");const input={...canonicalInput(request.input),freshnessDuration:freshness.duration,freshnessLockOutputsEnabled:true,freshnessLockSats:freshness.lockSats,freshnessUnlockHeight:freshness.unlockHeight};let media;try{if(input.media)media=(await inspectAgentMedia(input.media)).summary}catch(error){return blocked(error instanceof AgentMediaError?error.code:"media_inspection_unavailable")}const draft={schema:"mapapp-agent-paid-root-operation-v1",operationId:request.operationId,intakeId:crypto.randomUUID(),connection:binding,deployment,input,preparedAtHeight:freshness.tipHeight,phase:"prepared"};const nonce=`agent-local:${request.operationId}`;await options.store.persistReference({intentId:marker2(request.operationId),nonce,correlationTag:`intent:${nonce}`,clientIntentId:request.operationId,evidenceFormat:"mapapp_post_v2"},draft);return preparation(draft,media)})},execute(caller,operationId,signal){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(signal?.aborted)return blocked("request_cancelled");const currentRecords=await records(operationId,true);const drafts=currentRecords.map(prepared).filter((value)=>!!value);const draft=drafts.find((value)=>value.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="attempt_started")return reconcilePrepared(draft,currentRecords);const gates=await admission(caller,draft);if(typeof gates==="string")return blocked(gates);let publication;try{const{media,...fields}=draft.input;publication={...fields,...media?{media:(await inspectAgentMedia(media)).media}:{}}}catch(error){return blocked(error instanceof AgentMediaError?error.code:"media_inspection_unavailable")}if(signal?.aborted)return blocked("request_cancelled");await options.store.updateDraft(marker2(operationId),{...draft,phase:"attempt_started"});try{const result=await executePaidPostAction({provider:"agent-wallet-node",providerAttested:false,agentProofProfile:profile,client:options.client,clientIntentId:operationId,intentId:draft.intakeId,tabId:binding.connectionId,localIntentStore:options.store,draft:{schema:"mapapp-agent-paid-root-intake-v1",operationId,input:draft.input},evidenceFormat:"mapapp_post_v2",actionKind:"post",actionLabelEnabled:true,invoke:async(metadata)=>{const current=await admission(caller,draft);if(typeof current==="string"||signal?.aborted)throw new Error("agent_pre_dispatch_binding_or_prerequisite_changed");return options.invoke(publication,metadata)}});return{kind:"execution",operationId,result}}catch{return{kind:"recovery_required",operationId,reason:"original_operation_interrupted"}}})},status(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");const state=await inspector.inspect(caller);if(state.state!=="ready")return blocked(state.reason);const draft=(await records(operationId)).map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="prepared")return{kind:"status",operationId,phase:draft.phase,freshness:retainedFreshness(draft)};try{return{kind:"status",operationId,phase:draft.phase,freshness:retainedFreshness(draft),intake:await options.client.statusIntent(draft.intakeId)}}catch{return{kind:"recovery_required",operationId,reason:"original_intake_unavailable"}}})},reconcile(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");const state=await inspector.inspect(caller);if(state.state!=="ready")return blocked(state.reason);const currentRecords=await records(operationId,true);const draft=currentRecords.map(prepared).find((value)=>value?.operationId===operationId);return draft?reconcilePrepared(draft,currentRecords):blocked("operation_not_prepared")})},reportWalletRejection(caller,operationId,walletRejectedWithoutCreatingTransaction){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(walletRejectedWithoutCreatingTransaction!==true)return blocked("wallet_rejection_report_required");const currentRecords=await records(operationId,true);const draft=currentRecords.map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase!=="attempt_started")return blocked("operation_not_started");const matches=currentRecords.filter((record)=>record.reference.intentId===draft.intakeId);if(matches.length!==1)return{kind:"recovery_required",operationId,reason:"reference_conflict"};if(matches[0].returnedEvidence)return blocked("wallet_rejection_conflicts_with_evidence");try{let intake=await options.client.statusIntent(draft.intakeId);if(!walletRejectedWithoutTransaction(intake)){if(intake.subjectTxid!==null||intake.reportedSubjectTxid!==null||intake.invocationState!=="wallet_invocation_claimed"||intake.validationState!=="pending"||intake.projectionState!=="none"||!["awaiting_evidence","wallet_outcome_unknown","expired"].includes(String(intake.evidenceState))){return blocked("wallet_rejection_conflicts_with_evidence")}await options.client.recordOutcome(draft.intakeId,"wallet_rejected");intake=await options.client.statusIntent(draft.intakeId)}if(!walletRejectedWithoutTransaction(intake))return blocked("wallet_rejection_conflicts_with_evidence");return{kind:"rejection_recorded",operationId,intake,basis:"participant_report"}}catch{return{kind:"recovery_required",operationId,reason:"original_intake_unavailable"}}})}};function preparation(draft,media){return{kind:"prepared",operationId:draft.operationId,authority:participantAuthorityName(binding),freshness:retainedFreshness(draft),...media?{media}:{}}}}function walletRejectedWithoutTransaction(intake){return intake.invocationState==="intent_failed"&&intake.evidenceState==="rejected"&&intake.validationState==="rejected"&&intake.projectionState==="rejected"&&intake.rejectionCode==="wallet_rejected"&&intake.subjectTxid===null&&intake.reportedSubjectTxid===null}function uuid2(value){return typeof value==="string"&&/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(value)}function sameBinding(left,right){return!!left&&Object.keys(right).every((key)=>left[key]===right[key])}function canonicalInput(input){return{text:input.text,geohash:input.geohash,category:input.category,freshnessDuration:input.freshnessDuration,...input.media?{media:{contentType:input.media.contentType,base64:input.media.base64,byteSize:input.media.byteSize,sha256:input.media.sha256}}:{}}}function validInput(input){return!!input&&(input.media===undefined||validAgentMediaInput(input.media))&&typeof input.text==="string"&&!!input.text.trim()&&new TextDecoder().decode(new TextEncoder().encode(input.text))===input.text&&Object.keys(input).every((key)=>["text","geohash","category","freshnessDuration","media"].includes(key))&&new TextEncoder().encode(input.text).length<=16384&&typeof input.geohash==="string"&&POST_GEOHASH_PATTERN.test(input.geohash)&&MAPAPP_CATEGORY_TOKENS.includes(input.category)&&(input.freshnessDuration===undefined||["1D","7D","1M","3M"].includes(input.freshnessDuration))}function resolveFreshness(gates,duration){if(!duration||!["1D","7D","1M","3M"].includes(duration))return null;const blocks=gates.freshnessDurations?.[duration],height=gates.blockHeight,sats=gates.freshnessLockSats;if(!Number.isSafeInteger(height)||height<0||!Number.isSafeInteger(sats)||sats<=0||!Number.isSafeInteger(blocks)||blocks<=0||!Number.isSafeInteger(height+blocks)||height+blocks>=500000000)return null;return{duration,lockSats:sats,unlockHeight:height+blocks,tipHeight:height,durationBlocks:blocks}}function retainedFreshness(draft){return{duration:draft.input.freshnessDuration,lockSats:draft.input.freshnessLockSats,unlockHeight:draft.input.freshnessUnlockHeight,...draft.preparedAtHeight===undefined?{}:{tipHeight:draft.preparedAtHeight,durationBlocks:draft.input.freshnessUnlockHeight-draft.preparedAtHeight}}}import{sendAllBsv,sendBsv}from"@1sat/actions";var schema2="mapapp-agent-payment-operation-v1";var marker2=(id)=>`agent-payment:${id}`;var uuid3=(value)=>/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/u.test(value);function createAgentPaymentExecutor(options){const binding=Object.freeze({...options.binding}),deployment=new URL(options.deployment).origin;const profile=Object.freeze({...options.proofProfile});const wallet=options.context.wallet;if(!participantProfileMatches(binding,profile)||!options.store.updateDraft||!options.store.persistEvidence){throw new Error("agent_executor_configuration_invalid")}const inspector=createAgentWalletConnectionInspector({wallet,binding});let busy=false;const blocked=(reason)=>({kind:"blocked",reason});async function exclusive(work){if(busy)return blocked("execution_in_progress");busy=true;try{return await work()}finally{busy=false}}async function admission(caller){const state=await inspector.inspect(caller);return state.state==="ready"?null:state.reason}function prepared(record){const draft=record.draft;if(draft?.schema!==schema2)return null;if(!uuid3(draft.operationId)||!uuid3(draft.intakeId)||record.reference.intentId!==marker2(draft.operationId)||draft.deployment!==deployment||JSON.stringify(draft.connection)!==JSON.stringify(binding)||!["prepared","attempt_started"].includes(draft.phase)||!validated(draft.input))throw new Error("payment_binding_changed");return draft}function payment(input,evidence){const measured=evidence?inspectDirectBsvEvidence(evidence,input.to):null;return{to:input.to,max:"max"in input,satoshis:measured?.satoshis??("satoshis"in input?input.satoshis:null),fee:measured?.fee??null}}async function operationRecords(id){const retained=await options.store.list(marker2(id));const draft=retained.map(prepared).find((value)=>value?.operationId===id);return draft?.phase==="attempt_started"?[...retained,...await options.store.list(draft.intakeId)]:retained}function matching(draft,txid,bytes){const exact=normalizePaidPostActionOutcome("agent-wallet-node",{txid,tx:bytes});const output=inspectDirectBsvEvidence(bytes,draft.input.to);return exact.kind==="creation_evidence"&&!exact.anomaly&&!!output&&(!("satoshis"in draft.input)||output.satoshis===draft.input.satoshis)}async function recover(draft,records){if(draft.phase==="prepared")return blocked("operation_not_started");const matches=records.filter((record)=>record.reference.intentId===draft.intakeId);if(matches.length!==1)return{kind:"recovery_required",operationId:draft.operationId,reason:"original_payment_evidence_unavailable"};const evidence=matches[0].returnedEvidence;try{if(!evidence)return{kind:"recovery_required",operationId:draft.operationId,reason:"original_payment_evidence_unavailable"};const bytes=Uint8Array.from(evidence.atomicBeef);if(evidence.originator!==binding.originator||!matching(draft,evidence.txid,bytes))return blocked("payment_evidence_mismatch");const committed=await recoverNativeActionEvidence({wallet,record:matches[0],originator:binding.originator,checkpoint:null});if(!committed.ok)return{kind:"recovery_required",operationId:draft.operationId,reason:committed.code};await options.client.submitEvidence(draft.intakeId,evidence.txid,bytes);return{kind:"reconciled",operationId:draft.operationId,evidenceResubmitted:true,intake:await options.client.statusIntent(draft.intakeId),payment:payment(draft.input,bytes)}}catch{return{kind:"recovery_required",operationId:draft.operationId,reason:"original_intake_unavailable"}}}return{prepare(caller,request){return exclusive(async()=>{if(!uuid3(request.operationId))return blocked("invalid_operation_id");const input=validated(request.input);if(!input)return blocked("payment_input_invalid");const denied=await admission(caller);if(denied)return blocked(denied);if(!await options.ready().catch(()=>false))return blocked("payment_services_unavailable");const records=await options.store.list();const existing=records.map(prepared).find((draft)=>draft?.operationId===request.operationId);if(existing&&JSON.stringify(existing.input)!==JSON.stringify(input))return blocked("operation_input_conflict");if(existing?.phase==="attempt_started")return blocked("operation_already_started");if(!existing){if(records.some((record)=>record.reference.clientIntentId===request.operationId))return blocked("operation_input_conflict");await options.store.persistReference({intentId:marker2(request.operationId),clientIntentId:request.operationId,nonce:marker2(request.operationId),correlationTag:`intent:${marker2(request.operationId)}`},{schema:schema2,operationId:request.operationId,intakeId:crypto.randomUUID(),connection:binding,deployment,input,phase:"prepared"})}return{kind:"prepared",operationId:request.operationId,authority:participantAuthorityName(binding),payment:payment(input)}})},execute(caller,operationId,signal){return exclusive(async()=>{const denied=await admission(caller);if(denied)return blocked(denied);const records=await operationRecords(operationId);const draft=records.map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="attempt_started")return recover(draft,records);if(signal?.aborted)return blocked("request_cancelled");if(!await options.ready().catch(()=>false))return blocked("payment_services_unavailable");await options.store.updateDraft(marker2(operationId),{...draft,phase:"attempt_started"});try{const result=await executePaidPostAction({provider:"agent-wallet-node",providerAttested:false,agentProofProfile:profile,client:options.client,clientIntentId:operationId,intentId:draft.intakeId,tabId:binding.connectionId,localIntentStore:options.store,evidenceFormat:"mapapp_direct_bsv_v1",actionKind:"bsv_send",draft:{kind:"bsv_send",...draft.input},invoke:async(metadata)=>{if(signal?.aborted||await admission(caller)||!await options.ready().catch(()=>false))throw new Error("payment_prerequisite_changed");let dispatched=false;const fundingProvider={fund:async(args)=>{if(dispatched)throw new Error("payment_already_dispatched");dispatched=true;let returned;try{returned=await wallet.createAction({...args,labels:[...args.labels??[],`mapapp intent ${metadata.intentId}`],options:{...args.options,returnTXIDOnly:false}},binding.originator)}catch(error){const original=normalizePaidPostActionOutcome("agent-wallet-node",error);if(original.kind==="creation_evidence"&&!original.anomaly){await options.store.persistEvidence(draft.intakeId,original.txid,original.atomicBeef)}throw error}const exact=normalizePaidPostActionOutcome("agent-wallet-node",returned);if(exact.kind!=="creation_evidence"||exact.anomaly)throw new Error("original_payment_evidence_unavailable");await options.store.persistEvidence(draft.intakeId,exact.txid,exact.atomicBeef);if(!matching(draft,exact.txid,exact.atomicBeef))throw new Error("payment_evidence_mismatch");return{txid:exact.txid,tx:Array.from(exact.atomicBeef)}}};return"max"in draft.input?sendAllBsv.execute(options.context,{destination:draft.input.to,fundingProvider}):sendBsv.execute(options.context,{requests:[{address:draft.input.to,satoshis:draft.input.satoshis}],fundingProvider})}});const evidence=(await options.store.list(draft.intakeId)).find((record)=>record.reference.intentId===draft.intakeId)?.returnedEvidence;return{kind:"execution",operationId,result,payment:payment(draft.input,evidence&&Uint8Array.from(evidence.atomicBeef))}}catch{return{kind:"recovery_required",operationId,reason:"original_operation_interrupted"}}})},status(caller,operationId){return exclusive(async()=>{const denied=await admission(caller);if(denied)return blocked(denied);const records=await operationRecords(operationId);const draft=records.map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");const evidence=records.find((record)=>record.reference.intentId===draft.intakeId)?.returnedEvidence;try{return{kind:"status",operationId,phase:draft.phase,payment:payment(draft.input,evidence&&Uint8Array.from(evidence.atomicBeef)),...draft.phase==="attempt_started"?{intake:await options.client.statusIntent(draft.intakeId)}:{}}}catch{return{kind:"recovery_required",operationId,reason:"original_intake_unavailable"}}})},reconcile(caller,operationId){return exclusive(async()=>{const denied=await admission(caller);if(denied)return blocked(denied);const records=await operationRecords(operationId);const draft=records.map(prepared).find((value)=>value?.operationId===operationId);return draft?recover(draft,records):blocked("operation_not_prepared")})}}}function validated(input){if(!input||typeof input!=="object"||typeof input.to!=="string")return null;if("max"in input){if(input.max!==true||Object.keys(input).some((key)=>!["to","max"].includes(key)))return null;const result2=validateDirectBsvInput(input);return result2.ok?{to:result2.address,max:true}:null}if(!("satoshis"in input)||Object.keys(input).some((key)=>!["to","satoshis"].includes(key)))return null;const result=validateDirectBsvInput(input);return result.ok?{to:result.address,satoshis:result.satoshis}:null}async function relaySignedBeef(args){try{const localTxid=await readRelayBytesTxid(args.bytes,args.txid);if(localTxid!==args.txid){return{txid:args.txid,relayStatus:null,accepted:false,relayMessage:localTxid?`Wallet returned signed bytes for txid ${localTxid}, expected ${args.txid}.`:`Wallet returned signed bytes that MapApp could not decode for ${args.action}.`}}const relayResponse=await(args.fetch??fetch)(args.relayUrl,{method:"POST",headers:{"content-type":"application/octet-stream","x-mapapp-action":args.action,"x-mapapp-txid":args.txid},body:args.bytes});const relayBody=await relayResponse.json().catch(()=>null);const relayStatus=relayBody?.result?.txStatus??relayBody?.result?.status??null;const relayTxid=relayBody?.result?.txid??args.txid;if(!relayResponse.ok||!relayBody?.ok){return{txid:relayTxid,relayStatus,accepted:false,relayMessage:relayBody?.message??`MapApp relay returned HTTP ${relayResponse.status}`}}if(relayTxid!==args.txid){return{txid:relayTxid,relayStatus,accepted:false,relayMessage:`MapApp relay returned txid ${relayTxid}, expected ${args.txid}.`}}if(!isAcceptedRelayStatus(relayStatus)){return{txid:relayTxid,relayStatus,accepted:false,relayMessage:`MapApp relay did not return public network evidence for ${args.action}.`}}return{txid:relayTxid,relayStatus,accepted:true,relayMessage:null}}catch(error){return{txid:args.txid,relayStatus:null,accepted:false,relayMessage:error instanceof Error?error.message:"MapApp relay request failed."}}}async function readRelayBytesTxid(bytes,expectedTxid){const{Transaction}=await import("@bsv/sdk");const byteList=Array.from(bytes);try{return Transaction.fromBEEF(byteList,expectedTxid).id("hex")}catch{try{return Transaction.fromBinary(byteList).id("hex")}catch{return null}}}function isAcceptedRelayStatus(status){return status==="ACCEPTED_BY_NETWORK"||status==="SEEN_ON_NETWORK"||status==="SEEN_MULTIPLE_NODES"||status==="MINED"||status==="IMMUTABLE"}import{Hash,Utils}from"@bsv/sdk";var MAPAPP_LOCAL_INTENT_PROTOCOL=[2,"mapapp local intent index"];var LOCAL_INTENT_NAMESPACE_KEY_ID="mapapp local intent namespace v1";var LOCAL_INTENT_KEY_ID="mapapp local intent record v1";var LOCAL_INTENT_DATABASE_NAME="mapapp-local-intents";var DATABASE_VERSION=1;var RECORD_STORE="encrypted-intents";var ENVELOPE_VERSION=1;var CONTENT_VERSION=1;class LocalIntentStoreError extends Error{code;constructor(code,message,options){super(message,options);this.code=code;this.name="LocalIntentStoreError"}}function createLocalIntentStore(options){const originator=requireOriginator(options.originator);const repository=options.repository??createIndexedDbLocalIntentRepository(resolveIndexedDb(options.indexedDB));const now=options.now??(()=>new Date);const randomUUID=options.randomUUID??(()=>{if(typeof globalThis.crypto?.randomUUID!=="function"){throw new LocalIntentStoreError("secure_record_id_unavailable","Secure local intent record identifiers are unavailable.")}return globalThis.crypto.randomUUID()});let session;const lookup=(namespace,intent)=>`i1:${Utils.toHex(Hash.sha256(JSON.stringify([namespace,intent])))}:`;function remember(record,envelope){session?.set(record.recordId,{envelope:cloneEnvelope(envelope),record:structuredClone(record)})}async function readRecords(intentId){const walletNamespace=await namespaceId();let envelopes;try{envelopes=await repository.list(walletNamespace)}catch(cause){throw wrapError("local_intent_read_failed","The encrypted local intent index could not be read from IndexedDB.",cause)}if(intentId!==undefined){const prefix=lookup(walletNamespace,intentId);const indexed=envelopes.filter((envelope)=>envelope.recordId.startsWith(prefix));envelopes=indexed.length?indexed:envelopes.filter((envelope)=>!envelope.recordId.startsWith("i1:"))}const records=[];for(const envelope of envelopes){requireEnvelope(envelope,walletNamespace);const cached=session?.get(envelope.recordId);let record;if(cached&&cached.envelope.namespaceId===envelope.namespaceId&&cached.envelope.createdAt===envelope.createdAt&&cached.envelope.updatedAt===envelope.updatedAt&&cached.envelope.ciphertext.length===envelope.ciphertext.length&&cached.envelope.ciphertext.every((byte,index)=>byte===envelope.ciphertext[index])){record=structuredClone(cached.record)}else{let plaintext;try{const decrypted=await options.wallet.decrypt({ciphertext:[...envelope.ciphertext],protocolID:MAPAPP_LOCAL_INTENT_PROTOCOL,keyID:LOCAL_INTENT_KEY_ID,counterparty:"self"},originator);plaintext=requireBytes(decrypted.plaintext,"Wallet returned invalid local intent plaintext.",true)}catch(cause){throw wrapError("wallet_decryption_failed","The connected wallet could not decrypt its local intent index.",cause)}record=parseRecord(plaintext);if(record.recordId!==envelope.recordId||record.namespaceId!==envelope.namespaceId||record.createdAt!==envelope.createdAt||record.recordId.startsWith("i1:")&&!record.recordId.startsWith(lookup(walletNamespace,record.reference.intentId))){throw new LocalIntentStoreError("local_intent_envelope_mismatch","Encrypted local intent metadata does not match its authenticated content.")}remember(record,envelope)}if(intentId===undefined||record.reference.intentId===intentId)records.push(record)}return records.sort((left,right)=>left.createdAt.localeCompare(right.createdAt)||left.recordId.localeCompare(right.recordId))}async function namespaceId(){try{const result=await options.wallet.getPublicKey({protocolID:MAPAPP_LOCAL_INTENT_PROTOCOL,keyID:LOCAL_INTENT_NAMESPACE_KEY_ID,counterparty:"self",forSelf:true},originator);if(typeof result.publicKey!=="string"||!result.publicKey.trim()){throw new Error("Wallet returned an empty namespace public key.")}return result.publicKey}catch(cause){throw wrapError("wallet_namespace_unavailable","The connected wallet could not open its local intent namespace.",cause)}}return{namespaceId,async persistReference(reference,draft){requireReference(reference);const walletNamespace=await namespaceId();const timestamp=now().toISOString();const record={version:CONTENT_VERSION,recordId:`${options.indexedRecords?lookup(walletNamespace,reference.intentId):""}${randomUUID()}`,namespaceId:walletNamespace,createdAt:timestamp,updatedAt:timestamp,reference:{...reference},...draft===undefined?{}:{draft}};let ciphertext;try{const encrypted=await options.wallet.encrypt({plaintext:[...new TextEncoder().encode(JSON.stringify(record))],protocolID:MAPAPP_LOCAL_INTENT_PROTOCOL,keyID:LOCAL_INTENT_KEY_ID,counterparty:"self"},originator);ciphertext=requireBytes(encrypted.ciphertext,"Wallet returned invalid local intent ciphertext.")}catch(cause){throw wrapError("wallet_encryption_failed","The connected wallet refused or failed local intent encryption.",cause)}const envelope={envelopeVersion:ENVELOPE_VERSION,recordId:record.recordId,namespaceId:walletNamespace,createdAt:timestamp,updatedAt:timestamp,ciphertext};try{await repository.put(envelope);remember(record,envelope)}catch(cause){throw wrapError("local_intent_commit_failed","The encrypted local intent reference could not be committed to IndexedDB.",cause)}return record},list:readRecords,async withReadSession(work){if(session)throw new LocalIntentStoreError("local_intent_session_busy","Wait for the active participant call to finish.");session=new Map;try{return await work()}finally{session.clear();session=undefined}},async persistEvidence(intentId,txid,atomicBeef){if(!/^[0-9a-f]{64}$/.test(txid)||!atomicBeef.length||atomicBeef.length>2*1024*1024){throw new LocalIntentStoreError("local_evidence_invalid","Exact returned transaction evidence is invalid.")}const matches=await readRecords(intentId);if(matches.length!==1)throw new LocalIntentStoreError("local_evidence_reference_conflict","Exact evidence requires one wallet-owned intent reference.");const record=matches[0];if(record.returnedEvidence&&(record.returnedEvidence.txid!==txid||JSON.stringify(record.returnedEvidence.atomicBeef)!==JSON.stringify(Array.from(atomicBeef)))){throw new LocalIntentStoreError("local_evidence_conflict","The intent already retains different transaction evidence.")}if(record.returnedEvidence)return;const updated={...record,updatedAt:now().toISOString(),returnedEvidence:{txid,atomicBeef:Array.from(atomicBeef),originator}};const encrypted=await options.wallet.encrypt({plaintext:Array.from(new TextEncoder().encode(JSON.stringify(updated))),protocolID:MAPAPP_LOCAL_INTENT_PROTOCOL,keyID:LOCAL_INTENT_KEY_ID,counterparty:"self"},originator);const envelope={envelopeVersion:ENVELOPE_VERSION,recordId:record.recordId,namespaceId:record.namespaceId,createdAt:record.createdAt,updatedAt:updated.updatedAt,ciphertext:requireBytes(encrypted.ciphertext,"Wallet returned invalid local intent ciphertext.")};await repository.put(envelope);remember(updated,envelope)},async updateDraft(intentId,draft){const matches=await readRecords(intentId);if(matches.length!==1)throw new LocalIntentStoreError("local_draft_reference_conflict","Draft update requires one wallet-owned reference.");const record=matches[0];if(JSON.stringify(record.draft)===JSON.stringify(draft))return;const updated={...record,updatedAt:now().toISOString(),draft};const encrypted=await options.wallet.encrypt({plaintext:Array.from(new TextEncoder().encode(JSON.stringify(updated))),protocolID:MAPAPP_LOCAL_INTENT_PROTOCOL,keyID:LOCAL_INTENT_KEY_ID,counterparty:"self"},originator);const envelope={envelopeVersion:ENVELOPE_VERSION,recordId:record.recordId,namespaceId:record.namespaceId,createdAt:record.createdAt,updatedAt:updated.updatedAt,ciphertext:requireBytes(encrypted.ciphertext,"Wallet returned invalid local intent ciphertext.")};await repository.put(envelope);remember(updated,envelope)},async forgetCurrentWallet(){session?.clear();const walletNamespace=await namespaceId();try{await repository.deleteNamespace(walletNamespace)}catch(cause){throw wrapError("local_intent_forget_failed","The wallet's encrypted local intent index could not be removed.",cause)}}}}function createIndexedDbLocalIntentRepository(indexedDB){return{async put(record){requireEnvelope(record,record.namespaceId);const database=await openDatabase(indexedDB);try{await runTransaction(database,"readwrite",(store)=>store.put(cloneEnvelope(record)))}finally{database.close()}},async list(namespaceId){const database=await openDatabase(indexedDB);try{const records=await runTransaction(database,"readonly",(store)=>store.index("namespaceId").getAll(namespaceId));return records.map(cloneEnvelope)}finally{database.close()}},async deleteNamespace(namespaceId){const database=await openDatabase(indexedDB);try{await new Promise((resolve,reject)=>{const transaction=database.transaction(RECORD_STORE,"readwrite");const request=transaction.objectStore(RECORD_STORE).index("namespaceId").openKeyCursor(namespaceId);request.onsuccess=()=>{const cursor=request.result;if(!cursor)return;transaction.objectStore(RECORD_STORE).delete(cursor.primaryKey);cursor.continue()};transaction.oncomplete=()=>resolve();transaction.onerror=()=>reject(transaction.error??new Error("IndexedDB delete transaction failed."));transaction.onabort=()=>reject(transaction.error??new Error("IndexedDB delete transaction was aborted."))})}finally{database.close()}}}}function resolveIndexedDb(indexedDB){const resolved=indexedDB??(typeof globalThis.indexedDB==="undefined"?undefined:globalThis.indexedDB);if(!resolved)throw new LocalIntentStoreError("indexeddb_unavailable","IndexedDB is required for the encrypted local intent index.");return resolved}function openDatabase(indexedDB){return new Promise((resolve,reject)=>{const request=indexedDB.open(LOCAL_INTENT_DATABASE_NAME,DATABASE_VERSION);request.onupgradeneeded=()=>{const database=request.result;if(!database.objectStoreNames.contains(RECORD_STORE)){const store=database.createObjectStore(RECORD_STORE,{keyPath:"recordId"});store.createIndex("namespaceId","namespaceId",{unique:false})}};request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error??new Error("Could not open the local intent IndexedDB database."));request.onblocked=()=>reject(new Error("Close other MapApp tabs before upgrading the local intent index."))})}function runTransaction(database,mode,operation){return new Promise((resolve,reject)=>{const transaction=database.transaction(RECORD_STORE,mode);const request=operation(transaction.objectStore(RECORD_STORE));let result;request.onsuccess=()=>{result=request.result};request.onerror=()=>reject(request.error??new Error("IndexedDB request failed."));transaction.oncomplete=()=>resolve(result);transaction.onerror=()=>reject(transaction.error??new Error("IndexedDB transaction failed."));transaction.onabort=()=>reject(transaction.error??new Error("IndexedDB transaction was aborted."))})}function parseRecord(bytes){let value;try{value=JSON.parse(new TextDecoder("utf-8",{fatal:true}).decode(new Uint8Array(bytes)))}catch(cause){throw wrapError("local_intent_content_corrupt","Decrypted local intent content is invalid.",cause)}if(!isRecord(value)||value.version!==CONTENT_VERSION||!isNonEmptyString(value.recordId)||!isNonEmptyString(value.namespaceId)||!isIsoTimestamp(value.createdAt)||!isIsoTimestamp(value.updatedAt)||!isReference(value.reference)){throw new LocalIntentStoreError("local_intent_content_corrupt","Decrypted local intent content has an unsupported or corrupt shape.")}return value}function requireEnvelope(value,namespaceId){if(!isRecord(value)||value.envelopeVersion!==ENVELOPE_VERSION||!isNonEmptyString(value.recordId)||value.namespaceId!==namespaceId||!isIsoTimestamp(value.createdAt)||!isIsoTimestamp(value.updatedAt)){throw new LocalIntentStoreError("local_intent_envelope_corrupt","Encrypted local intent metadata has an unsupported or corrupt shape.")}requireBytes(value.ciphertext,"Encrypted local intent ciphertext is invalid.")}function requireReference(value){if(!isReference(value)||value.correlationTag!==`intent:${value.nonce}`){throw new LocalIntentStoreError("local_intent_reference_invalid","The caller-known local intent reference is invalid.")}}function isReference(value){return isRecord(value)&&isNonEmptyString(value.intentId)&&isNonEmptyString(value.nonce)&&isNonEmptyString(value.correlationTag)&&isNonEmptyString(value.clientIntentId)&&(value.evidenceFormat===undefined||value.evidenceFormat==="mapapp_post_v1"||value.evidenceFormat==="mapapp_post_v2"||value.evidenceFormat==="mapapp_social_v2"||value.evidenceFormat==="mapapp_sponsored_v2"||value.evidenceFormat==="mapapp_upgrade_v2"||value.evidenceFormat==="mapapp_direct_bsv_v1"||value.evidenceFormat==="mapapp_bap_alias_v1")}function requireBytes(value,message,allowEmpty=false){if(!Array.isArray(value)||!allowEmpty&&value.length===0||value.some((byte)=>!Number.isInteger(byte)||byte<0||byte>255)){throw new LocalIntentStoreError("local_intent_bytes_invalid",message)}return[...value]}function cloneEnvelope(value){return{...value,ciphertext:[...value.ciphertext]}}function requireOriginator(value){const originator=value.trim();if(!originator)throw new LocalIntentStoreError("originator_required","A MapApp originator is required for wallet-owned local encryption.");return originator}function isIsoTimestamp(value){return typeof value==="string"&&Number.isFinite(Date.parse(value))}function isNonEmptyString(value){return typeof value==="string"&&value.length>0}function isRecord(value){return typeof value==="object"&&value!==null}function wrapError(code,message,cause){if(cause instanceof LocalIntentStoreError)return cause;const detail=cause instanceof Error&&cause.message?` ${cause.message}`:"";return new LocalIntentStoreError(code,`${message}${detail}`,{cause})}async function executePaidRootPost(actionContext,input,metadata,execute){if(!input.text.trim())return{error:"MapApp Post v2 requires non-empty text."};if(input.freshnessLockOutputsEnabled!==true||!input.freshnessUnlockHeight||!input.freshnessLockSats||!input.freshnessDuration){return{error:"MapApp Post v2 freshness output settings are incomplete."}}const[{applyBapAip,executeTrackedAction,P1SAT_PROTOCOL}]=await Promise.all([import("@1sat/actions")]);const unsignedRoot=await preparePaidRootPostScriptObject(input);const signedRoot=await applyBapAip(actionContext,unsignedRoot);const lockKey=await actionContext.wallet.getPublicKey({protocolID:P1SAT_PROTOCOL,keyID:"lock",counterparty:"self",forSelf:true});const args=await assemblePaidRootPostAction(input,metadata,signedRoot.toHex(),lockKey.publicKey);const result=await(execute??((args2)=>executeTrackedAction(actionContext.wallet,args2)))(args);if(result.error==="broadcast-success"){const{recoverPaidPostEvidence:recoverPaidPostEvidence2}=await import("./chunk-c74qq5ek.js");const recovered=await recoverPaidPostEvidence2({wallet:actionContext.wallet,correlationTag:metadata.outputTags[0],evidenceFormat:"mapapp_post_v2"});if(recovered.kind==="recovered"){return{txid:recovered.txid,tx:Array.from(recovered.atomicBeef)}}}return{txid:result.txid,tx:result.tx,error:result.error}}async function preparePaidRootPostScriptObject(input){const[{B,BitCom,MAP:MAPTemplate},{OP,Script}]=await Promise.all([import("@1sat/templates"),import("@bsv/sdk")]);const textProtocol=oneBitComProtocol(BitCom,B.text(input.text,"text/plain"));const mapProtocol=oneBitComProtocol(BitCom,MAPTemplate.set({app:"mapapp",type:"post",context:"geohash",contextValue:input.geohash,category:input.category}));const bareUnsignedRoot=new BitCom([{...textProtocol,pos:0},{...mapProtocol,pos:1}]).lock();return new Script([{op:OP.OP_FALSE},...bareUnsignedRoot.chunks])}async function assemblePaidRootPostAction(input,metadata,signedRootScriptHex,lockPublicKey){const[{BSOCIAL_BASKET,P1SAT_PROTOCOL},{B,BitCom,Lock},{OP,PublicKey,Script},{LOCK_BASKET}]=await Promise.all([import("@1sat/actions"),import("@1sat/templates"),import("@bsv/sdk"),import("@1sat/types")]);const locker=PublicKey.fromString(lockPublicKey).toAddress();const freshnessScript=Lock.lock(locker,input.freshnessUnlockHeight);const outputs=[{lockingScript:signedRootScriptHex,satoshis:0,outputDescription:"MapApp Post v2 social root",basket:BSOCIAL_BASKET,tags:[...metadata.outputTags]}];if(input.media){outputs.push({lockingScript:new Script([{op:OP.OP_FALSE},...new BitCom([{...oneBitComProtocol(BitCom,B.binary(input.media.bytes,input.media.contentType)),pos:0}]).lock().chunks]).toHex(),satoshis:0,outputDescription:`MapApp Post v2 ${input.media.kind} media`})}outputs.push({lockingScript:freshnessScript.toHex(),satoshis:input.freshnessLockSats,outputDescription:`Genesis freshness lock until block ${input.freshnessUnlockHeight}`,basket:LOCK_BASKET,tags:["app:mapapp","type:freshness","kind:genesis",`until:${input.freshnessUnlockHeight}`,`duration:${input.freshnessDuration}`],customInstructions:JSON.stringify({protocolID:P1SAT_PROTOCOL,keyID:"lock",app:"mapapp",kind:"genesis-freshness"})});return{description:input.media?`MapApp Post v2 with ${input.media.kind} media`:"MapApp Post v2 text post",outputs,labels:metadata.labels,options:{acceptDelayedBroadcast:false,randomizeOutputs:false}}}function oneBitComProtocol(BitComTemplate,script){const decoded=BitComTemplate.decode(script);if(!decoded||decoded.protocols.length!==1){throw new Error("Public protocol template did not produce one BitCom section.")}return decoded.protocols[0]}import{FileBackupProvider,FileRestoreReader,Zip,ZipDeflate,unzip}from"@1sat/wallet";import{createSyncMap}from"@bsv/wallet-toolbox-client";import{PrivateKey,Utils as Utils2}from"@bsv/sdk";var STORAGE_COUNT_METHODS=[{method:"countUsers",table:"users"},{method:"countProvenTxs",table:"provenTxs"},{method:"countProvenTxReqs",table:"provenTxReqs"},{method:"countOutputBaskets",table:"outputBaskets"},{method:"countTransactions",table:"transactions"},{method:"countCommissions",table:"commissions"},{method:"countOutputs",table:"outputs"},{method:"countOutputTags",table:"outputTags"},{method:"countOutputTagMaps",table:"outputTagMaps"},{method:"countTxLabels",table:"txLabels"},{method:"countTxLabelMaps",table:"txLabelMaps"},{method:"countCertificates",table:"certificates"},{method:"countCertificateFields",table:"certificateFields"},{method:"countSyncStates",table:"syncStates"},{method:"countMonitorEvents",table:"monitorEvents"},{method:"countChangeInputs",table:"changeInputs"}];async function inspectWalletStorageTarget(storage){if(storage===null||storage===undefined||typeof storage!=="object"&&typeof storage!=="function"){throw new Error("A public restore-target storage provider is required.")}try{const settings=readStorageSettings(storage);const counts={};for(const entry of STORAGE_COUNT_METHODS){const method=storage[entry.method];if(typeof method!=="function"){throw new Error(`Public restore-target count ${entry.method} is unavailable.`)}const count=entry.method==="countChangeInputs"?await readChangeInputCount(storage,method):await method.call(storage,{partial:{}});if(!Number.isSafeInteger(count)||count<0){throw new Error(`Public restore-target count ${entry.method} is malformed.`)}counts[entry.table]=count}const isEmpty=Object.values(counts).every((count)=>count===0);return{chain:settings.chain,identityKey:isEmpty?null:undefined,tableCounts:counts,isEmpty}}catch(error){if(error instanceof Error&&error.message.startsWith("Public restore-target"))throw error;const detail=error instanceof Error?error.message:String(error??"unknown error");throw new Error(`Public restore-target inspection failed: ${detail.slice(0,240)}`,{cause:error})}}async function readChangeInputCount(storage,countChangeInputs){const findUsers=storage.findUsers;const findOutputBaskets=storage.findOutputBaskets;if(typeof findUsers!=="function"||typeof findOutputBaskets!=="function"){throw new Error("Public user and basket enumeration is required to prove change-input emptiness.")}const users=await findUsers.call(storage,{partial:{}});const baskets=await findOutputBaskets.call(storage,{partial:{}});if(!Array.isArray(users)||!Array.isArray(baskets)){throw new Error("Public user or basket enumeration returned malformed rows.")}const count=countChangeInputs;let total=0;for(const user of users){const userId=isRecord2(user)?user.userId:undefined;if(!Number.isSafeInteger(userId)||userId<0){throw new Error("Public user enumeration returned a malformed row.")}for(const basket of baskets){const basketId=isRecord2(basket)?basket.basketId:undefined;if(!Number.isSafeInteger(basketId)||basketId<0){throw new Error("Public basket enumeration returned a malformed row.")}const pairCount=await count.call(storage,userId,basketId,false);if(!Number.isSafeInteger(pairCount)||pairCount<0||!Number.isSafeInteger(total+pairCount)){throw new Error("Public change-input count is malformed.")}total+=pairCount}}return total}function readStorageSettings(storage){const getter=storage.getSettings??storage.getActiveSettings;if(typeof getter!=="function")throw new Error("Public restore-target storage settings are unavailable.");const settings=getter.call(storage);const chain=settings?.chain;if(chain!=="main"&&chain!=="test")throw new Error("Public restore-target storage chain is unavailable or unsupported.");return{chain}}function isRecord2(value){return Boolean(value)&&typeof value==="object"&&!Array.isArray(value)}var WALLET_DATA_ZIP_MAX_COMPRESSED_BYTES=512*1024*1024;var WALLET_DATA_ZIP_MAX_ENTRIES=4096;var WALLET_DATA_ZIP_MAX_EXPANDED_ENTRY_BYTES=64*1024*1024;var WALLET_DATA_ZIP_MAX_EXPANDED_BYTES=512*1024*1024;var WALLET_DATA_ZIP_LIMITS=Object.freeze({maxCompressedBytes:WALLET_DATA_ZIP_MAX_COMPRESSED_BYTES,maxEntries:WALLET_DATA_ZIP_MAX_ENTRIES,maxExpandedEntryBytes:WALLET_DATA_ZIP_MAX_EXPANDED_ENTRY_BYTES,maxExpandedBytes:WALLET_DATA_ZIP_MAX_EXPANDED_BYTES});function resolveWalletDataZipLimits(overrides={}){const values={maxCompressedBytes:overrides.maxCompressedBytes??WALLET_DATA_ZIP_LIMITS.maxCompressedBytes,maxEntries:overrides.maxEntries??WALLET_DATA_ZIP_LIMITS.maxEntries,maxExpandedEntryBytes:overrides.maxExpandedEntryBytes??WALLET_DATA_ZIP_LIMITS.maxExpandedEntryBytes,maxExpandedBytes:overrides.maxExpandedBytes??WALLET_DATA_ZIP_LIMITS.maxExpandedBytes};for(const[name,value]of Object.entries(values)){const ceiling=WALLET_DATA_ZIP_LIMITS[name];if(!Number.isSafeInteger(value)||value<1||value>ceiling){throw new Error(`Wallet-data ZIP ${name} must be a positive safe integer no greater than its production ceiling.`)}}if(values.maxExpandedEntryBytes>values.maxExpandedBytes){throw new Error("Wallet-data ZIP per-entry expansion limit cannot exceed total expansion limit.")}return values}var ENTITY_MAPPINGS=[{name:"provenTx",property:"provenTxs"},{name:"outputBasket",property:"outputBaskets"},{name:"outputTag",property:"outputTags"},{name:"txLabel",property:"txLabels"},{name:"transaction",property:"transactions"},{name:"output",property:"outputs"},{name:"txLabelMap",property:"txLabelMaps"},{name:"outputTagMap",property:"outputTagMaps"},{name:"certificate",property:"certificates"},{name:"certificateField",property:"certificateFields"},{name:"commission",property:"commissions"},{name:"provenTxReq",property:"provenTxReqs"}];var DEFAULT_MAX_CHUNKS=1e5;var AMBIGUOUS_RESTORE_TARGETS=new WeakSet;function requireStorage(value){if(!value||typeof value!=="object"&&typeof value!=="function"){throw new Error("A WalletStorageManager is required for wallet-data ZIP portability.")}const candidate=value;if(typeof candidate.getSettings!=="function"||typeof candidate.getAuth!=="function"||typeof candidate.syncToWriter!=="function"||typeof candidate.syncFromReader!=="function"){throw new Error("The upstream WalletStorageManager does not expose the required wallet-data sync operations.")}return candidate}function requireRestoreStorage(value){if(!value||typeof value!=="object"&&typeof value!=="function"){throw new Error("A WalletStorageManager is required for wallet-data ZIP restore.")}const candidate=value;if(typeof candidate.syncFromReader!=="function"){throw new Error("The upstream WalletStorageManager does not expose syncFromReader for wallet-data ZIP restore.")}return candidate}function assertIdentityKey(identityKey){if(!/^(02|03)[0-9a-fA-F]{64}$/.test(identityKey)){throw new Error("The wallet-data ZIP identity key is invalid.")}}function assertManifest(value){if(!value||typeof value!=="object"){throw new Error("Wallet-data ZIP manifest is missing.")}const manifest=value;if(manifest.version!==1||!Number.isSafeInteger(manifest.chunkCount)||(manifest.chunkCount??-1)<1||manifest.chain!=="main"&&manifest.chain!=="test"||typeof manifest.createdAt!=="string"||typeof manifest.identityKey!=="string"){throw new Error("Wallet-data ZIP manifest is invalid.")}assertIdentityKey(manifest.identityKey);if(!Number.isFinite(Date.parse(manifest.createdAt))){throw new Error("Wallet-data ZIP manifest has an invalid creation timestamp.")}}function assertZipEntryInfo(file,limits){if(!Number.isSafeInteger(file.size)||file.size<0||!Number.isSafeInteger(file.originalSize)||file.originalSize<0){throw new Error("Wallet-data ZIP contains an unbounded entry size.")}if(file.originalSize>limits.maxExpandedEntryBytes){throw new Error("Wallet-data ZIP entry exceeds the expanded-size limit.")}if(file.compression!==0&&file.compression!==8){throw new Error("Wallet-data ZIP uses an unsupported compression method.")}}function assertZipResourceMetadata(file,count,expandedBytes,limits){if(count>limits.maxEntries){throw new Error("Wallet-data ZIP contains too many entries.")}assertZipEntryInfo(file,limits);const nextExpandedBytes=expandedBytes+file.originalSize;if(!Number.isSafeInteger(nextExpandedBytes)||nextExpandedBytes>limits.maxExpandedBytes){throw new Error("Wallet-data ZIP exceeds the total expanded-size limit.")}return nextExpandedBytes}function assertExpandedEntries(entries,limits){let total=0;let count=0;for(const value of Object.values(entries)){count+=1;if(count>limits.maxEntries||value.byteLength>limits.maxExpandedEntryBytes){throw new Error("Wallet-data ZIP expanded entry limits were exceeded.")}total+=value.byteLength;if(!Number.isSafeInteger(total)||total>limits.maxExpandedBytes){throw new Error("Wallet-data ZIP total expanded-size limit was exceeded.")}}}function safeRestoreErrorDetail(error){let detail=error instanceof Error?error.message:String(error??"");detail=detail.replace(/[5KLc9][1-9A-HJ-NP-Za-km-z]{50,51}/g,"[redacted]").replace(/\b(?:02|03)[0-9a-f]{64}\b/gi,"[redacted]");return detail.length>180?`${detail.slice(0,177)}...`:detail}function makeAmbiguousRestoreError(error){const detail=safeRestoreErrorDetail(error);return new Error(detail?`Wallet-data ZIP restore became ambiguous after syncFromReader began; the target may be partially restored (${detail}). Do not retry this target.`:"Wallet-data ZIP restore became ambiguous after syncFromReader began; the target may be partially restored. Do not retry this target.")}function unzipArchive(bytes,limits){if(bytes.byteLength>limits.maxCompressedBytes){throw new Error("Wallet-data ZIP exceeds the 512 MiB compressed-size limit.")}return new Promise((resolve,reject)=>{let count=0;let expandedBytes=0;const names=new Set;const inspectOnly=(file)=>{if(names.has(file.name)){throw new Error("Wallet-data ZIP contains duplicate entry names.")}names.add(file.name);count+=1;expandedBytes=assertZipResourceMetadata(file,count,expandedBytes,limits);return false};try{unzip(bytes,{filter:inspectOnly},(error)=>{if(error){reject(error);return}try{unzip(bytes,(expandError,entries)=>{if(expandError){reject(expandError);return}try{assertExpandedEntries(entries,limits);resolve(entries)}catch(entryError){reject(entryError)}})}catch(expandError){reject(expandError)}})}catch(metadataError){reject(metadataError)}})}function hasData(chunk){return ENTITY_MAPPINGS.some(({property})=>{const value=chunk[property];if(value===undefined)return false;if(!Array.isArray(value)){throw new Error(`Wallet-data sync chunk field ${property} is not an array.`)}return value.length>0})}function parseManifest(entries){const manifestBytes=entries["manifest.json"];if(!manifestBytes){throw new Error("Wallet-data ZIP has no manifest.json.")}let value;try{value=JSON.parse(new TextDecoder().decode(manifestBytes))}catch{throw new Error("Wallet-data ZIP manifest is not valid JSON.")}assertManifest(value);return value}function assertOnlyStandardEntries(entries,manifest){const names=Object.keys(entries).sort();const chunkIndexes=names.filter((name)=>name!=="manifest.json").map((name)=>{const match=/^chunk-(\d+)\.bin$/.exec(name);if(!match)return null;const index=Number(match[1]);return Number.isSafeInteger(index)&&String(index).padStart(4,"0")===match[1]?index:null});chunkIndexes.sort((left,right)=>(left??Number.MAX_SAFE_INTEGER)-(right??Number.MAX_SAFE_INTEGER));const exactChunkSequence=chunkIndexes.length===manifest.chunkCount&&chunkIndexes.every((index,expectedIndex)=>index===expectedIndex);if(names.length!==manifest.chunkCount+1||!exactChunkSequence){throw new Error("Wallet-data ZIP must contain only upstream manifest and sync chunks.")}}function assertFreshRestoreInspection(inspection,expectedChain){if(inspection===undefined)return;if(!inspection||typeof inspection!=="object"||Array.isArray(inspection)){throw new Error("Wallet-data ZIP restore target freshness proof is malformed.")}if(inspection.chain===null||inspection.chain===undefined){throw new Error("Wallet-data ZIP restore target chain could not be verified.")}if(inspection.chain!==expectedChain){throw new Error("Wallet-data ZIP restore target chain does not match the archive chain.")}if(inspection.identityKey!==undefined&&inspection.identityKey!==null&&inspection.identityKey!==""){throw new Error("Wallet-data ZIP restore target is not fresh; an existing wallet identity was found.")}let allCountsZero=false;if(inspection.tableCounts!==undefined){if(!inspection.tableCounts||typeof inspection.tableCounts!=="object"||Array.isArray(inspection.tableCounts)){throw new Error("Wallet-data ZIP restore target counts are malformed.")}allCountsZero=Object.keys(inspection.tableCounts).length>0;for(const count of Object.values(inspection.tableCounts)){if(!Number.isSafeInteger(count)||count<0){throw new Error("Wallet-data ZIP restore target counts are malformed.")}if(count!==0){throw new Error("Wallet-data ZIP restore target is not empty; no import was attempted.")}}}if(inspection.isEmpty===false||inspection.isEmpty!==true&&!allCountsZero){throw new Error("Wallet-data ZIP restore target did not prove whole-store emptiness.")}}async function proveFreshRestoreTarget(options,expectedChain){if(options.inspectTarget!==undefined&&options.assertTargetFresh!==undefined){throw new Error("Wallet-data ZIP restore accepts only one target freshness proof.")}if(options.inspectTarget!==undefined||options.assertTargetFresh!==undefined){const inspect=options.inspectTarget??options.assertTargetFresh;try{assertFreshRestoreInspection(await inspect(),expectedChain);return}catch(error){if(error instanceof Error&&error.message.includes("no import was attempted"))throw error;throw error instanceof Error?error:new Error("Wallet-data ZIP restore target freshness proof failed.")}}if(!options.targetStorage){throw new Error("Wallet-data ZIP restore requires a whole-store empty-target proof before syncFromReader.")}assertFreshRestoreInspection(await inspectWalletStorageTarget(options.targetStorage),expectedChain)}class MapAppFileBackupProvider extends FileBackupProvider{expectedIdentityKey;targetSettings;maxChunks;syncMap=createSyncMap();sourceIdentityKey;sourceName;sourceUserId;initialized=false;terminal=false;failed=false;constructor(zip,settings,identityKey,options={}){super(zip,settings,identityKey);assertIdentityKey(identityKey);const maxChunks=options.maxChunks??DEFAULT_MAX_CHUNKS;if(!Number.isSafeInteger(maxChunks)||maxChunks<1){throw new Error("The wallet-data ZIP chunk limit must be a positive safe integer.")}this.expectedIdentityKey=identityKey;this.targetSettings=settings;this.maxChunks=maxChunks}hasReachedTerminalChunk(){return this.terminal}async findOrInsertSyncStateAuth(auth,storageIdentityKey,storageName){if(this.failed){throw new Error("The wallet-data ZIP export already failed; create a new provider.")}if(auth.identityKey!==this.expectedIdentityKey){throw new Error("Wallet-data export identity does not match the authenticated wallet.")}if(this.terminal){throw new Error("The wallet-data ZIP provider cannot be reused after completion; create a new provider.")}if(typeof storageIdentityKey!=="string"||storageIdentityKey.length===0||typeof storageName!=="string"||storageName.length===0){throw new Error("The wallet-data source storage settings are invalid.")}const userId=auth.userId??1;if(!Number.isSafeInteger(userId)||userId<0){throw new Error("The wallet-data source user id is invalid.")}const sourceChanged=this.sourceIdentityKey!==storageIdentityKey||this.sourceName!==storageName||this.sourceUserId!==userId;if(this.initialized&&sourceChanged){throw new Error("Wallet-data source changed during export; create a new provider.")}const isNew=!this.initialized;if(isNew){this.syncMap=createSyncMap();this.sourceIdentityKey=storageIdentityKey;this.sourceName=storageName;this.sourceUserId=userId;this.initialized=true}const now=new Date;return{syncState:{syncStateId:0,created_at:now,updated_at:now,userId,storageIdentityKey,storageName,init:false,refNum:"file-backup",status:"unknown",syncMap:JSON.stringify(this.syncMap)},isNew}}requestOffsets(args){if(!Array.isArray(args.offsets)||args.offsets.length!==ENTITY_MAPPINGS.length){throw new Error("Wallet-data sync did not provide the complete upstream entity offset sequence.")}const offsets=new Map;for(const[index,expected]of ENTITY_MAPPINGS.entries()){const actual=args.offsets[index];if(!actual||actual.name!==expected.name||!Number.isSafeInteger(actual.offset)||actual.offset<0){throw new Error(`Wallet-data sync offsets are not in upstream entity order at ${expected.name}.`)}if(offsets.has(actual.name)){throw new Error(`Wallet-data sync offsets contain a duplicate ${actual.name} entity.`)}offsets.set(actual.name,actual.offset)}return offsets}validateChunk(args,chunk){if(!this.initialized||this.sourceIdentityKey===undefined||this.sourceName===undefined){throw new Error("Wallet-data sync state was not initialized before the first chunk.")}if(args.identityKey!==this.expectedIdentityKey||chunk.userIdentityKey!==this.expectedIdentityKey){throw new Error("Wallet-data sync chunk identity does not match the export identity.")}if(args.fromStorageIdentityKey!==this.sourceIdentityKey||chunk.fromStorageIdentityKey!==args.fromStorageIdentityKey){throw new Error("Wallet-data sync chunk source does not match the initialized source storage.")}if(chunk.toStorageIdentityKey!==args.toStorageIdentityKey){throw new Error("Wallet-data sync chunk destination does not match the requested destination.")}if(this.targetSettings.storageIdentityKey!==args.toStorageIdentityKey){throw new Error("Wallet-data sync destination does not match the writer settings.")}const offsets=this.requestOffsets(args);for(const{name}of ENTITY_MAPPINGS){const expectedOffset=this.syncMap[name].count;const requestedOffset=offsets.get(name);if(requestedOffset!==expectedOffset){throw new Error(`Wallet-data sync did not converge for ${name}: expected offset ${expectedOffset}, received ${requestedOffset}.`)}}return{offsets,data:hasData(chunk)}}async processSyncChunk(args,chunk){if(this.failed){throw new Error("The wallet-data ZIP export already failed; create a new provider.")}if(this.terminal){throw new Error("Wallet-data sync produced a chunk after the terminal chunk.")}if(this.getChunkCount()>=this.maxChunks){this.failed=true;throw new Error("Wallet-data sync did not converge before the ZIP chunk limit was reached.")}try{const{offsets,data}=this.validateChunk(args,chunk);const result=await super.processSyncChunk(args,chunk);if(result.done===data){throw new Error("Upstream FileBackupProvider returned an inconsistent sync completion result.")}for(const{name,property}of ENTITY_MAPPINGS){const rows=chunk[property];if(rows===undefined)continue;if(!Array.isArray(rows)){throw new Error(`Wallet-data sync chunk field ${property} is not an array.`)}if(rows.length>0){this.syncMap[name].count=(offsets.get(name)??0)+rows.length}}if(data&&this.syncMapProgressTotal()===0){throw new Error("Wallet-data sync did not advance any entity offset.")}if(!data)this.terminal=true;return result}catch(error){this.failed=true;throw error}}syncMapProgressTotal(){return ENTITY_MAPPINGS.reduce((total,{name})=>total+this.syncMap[name].count,0)}}async function inspectWalletDataZip(bytes,expected,options={}){if(!(bytes instanceof Uint8Array)||bytes.byteLength===0){throw new Error("Choose a non-empty wallet-data ZIP.")}assertIdentityKey(expected.identityKey);const limits=resolveWalletDataZipLimits(options.limits);const entries=await unzipArchive(bytes,limits);const manifest=parseManifest(entries);if(manifest.identityKey!==expected.identityKey){throw new Error("Wallet-data ZIP identity does not match the expected wallet identity.")}if(manifest.chain!==expected.chain){throw new Error("Wallet-data ZIP chain does not match the expected wallet chain.")}assertOnlyStandardEntries(entries,manifest);return{bytes:new Uint8Array(bytes),manifest}}function collectZip(){const pieces=[];let finish;let fail;const done=new Promise((resolve,reject)=>{finish=resolve;fail=reject});let settled=false;const zip=new Zip((error,data,final)=>{if(settled)return;if(error){settled=true;fail(error);return}if(data&&data.byteLength>0)pieces.push(data);if(final){settled=true;const size=pieces.reduce((total,piece)=>total+piece.byteLength,0);const bytes=new Uint8Array(size);let offset=0;for(const piece of pieces){bytes.set(piece,offset);offset+=piece.byteLength}finish(bytes)}});return{zip,done}}async function exportWalletDataZip(storageValue,expected,options={}){const storage=requireStorage(storageValue);assertIdentityKey(expected.identityKey);const settings=storage.getSettings();if(settings.chain!==expected.chain){throw new Error("Wallet storage chain does not match export chain.")}const limits=resolveWalletDataZipLimits(options.limits);const{zip,done}=collectZip();const writer=new MapAppFileBackupProvider(zip,settings,expected.identityKey,options);const auth=await storage.getAuth(true);if(auth.identityKey!==expected.identityKey){throw new Error("Wallet-data export identity does not match the authenticated wallet.")}await storage.syncToWriter(auth,writer);if(!writer.hasReachedTerminalChunk()){throw new Error("Wallet-data sync returned before a terminal empty chunk; export did not converge.")}const manifest={version:1,createdAt:new Date().toISOString(),chain:expected.chain,identityKey:expected.identityKey,chunkCount:writer.getChunkCount()};const entry=new ZipDeflate("manifest.json",{level:6});zip.add(entry);entry.push(new TextEncoder().encode(JSON.stringify(manifest)),true);zip.end();const bytes=await done;return await inspectWalletDataZip(bytes,expected,{limits})}async function preflightReader(entries,manifest,expectedIdentityKey){const reader=new FileRestoreReader(entries,manifest);const readerSettings=await reader.makeAvailable();for(let index=0;index<manifest.chunkCount;index++){const chunk=await reader.getSyncChunk({fromStorageIdentityKey:readerSettings.storageIdentityKey,toStorageIdentityKey:"wallet-data-validation",identityKey:expectedIdentityKey,maxRoughSize:1e7,maxItems:1000,offsets:[]});if(chunk.userIdentityKey!==expectedIdentityKey){throw new Error("Wallet-data ZIP chunk identity does not match the expected wallet identity.")}}reader.reset();return reader}async function restoreWalletDataZip(storageValue,bytes,expected,options={}){const storage=requireRestoreStorage(storageValue);const targetCandidate=options.targetStorage??storage;if(typeof targetCandidate!=="object"&&typeof targetCandidate!=="function"||targetCandidate===null){throw new Error("Wallet-data ZIP restore target freshness provider is invalid.")}const targetKey=targetCandidate;if(AMBIGUOUS_RESTORE_TARGETS.has(targetKey)){throw new Error("Wallet-data ZIP restore target has an ambiguous prior sync failure; do not retry this target.")}const limits=resolveWalletDataZipLimits(options.limits);const inspected=await inspectWalletDataZip(bytes,expected,{limits});await proveFreshRestoreTarget(options,expected.chain);const entries=await unzipArchive(inspected.bytes,limits);const reader=await preflightReader(entries,inspected.manifest,expected.identityKey);await options.beforeSync?.();await proveFreshRestoreTarget(options,expected.chain);AMBIGUOUS_RESTORE_TARGETS.add(targetKey);try{await storage.syncFromReader(expected.identityKey,reader)}catch(error){throw makeAmbiguousRestoreError(error)}AMBIGUOUS_RESTORE_TARGETS.delete(targetKey);return inspected.manifest}import{Database}from"bun:sqlite";import{resolve}from"path";var AGENT_LOCAL_INTENT_MAX_CIPHERTEXT_BYTES=16*1024*1024;var AGENT_LOCAL_INTENT_MAX_NAMESPACE_BYTES=64*1024*1024;var AGENT_LOCAL_INTENT_MAX_NAMESPACE_RECORDS=1000;var MAX_IDENTIFIER_LENGTH=512;var APPLICATION_ID=1296124238;var SCHEMA_VERSION=1;var ENVELOPE_KEYS=["envelopeVersion","recordId","namespaceId","createdAt","updatedAt","ciphertext"];class AgentLocalIntentRepositoryError extends Error{code;constructor(code,message,options){super(message,options);this.code=code;this.name="AgentLocalIntentRepositoryError"}}function openAgentLocalIntentRepository(filename){if(typeof filename!=="string"||!filename.trim()||filename.includes("\x00")||filename===":memory:"||filename.startsWith("file:")){throw error("local_intent_path_invalid","A dedicated persistent local intent database filename is required.")}let database;try{database=new Database(resolve(filename),{create:true,strict:true});database.exec("PRAGMA busy_timeout = 0; PRAGMA locking_mode = EXCLUSIVE;");const applicationId=getRow(database,"PRAGMA application_id").application_id;const version=getRow(database,"PRAGMA user_version").user_version;const objects=getRows(database,"SELECT name, type FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' ORDER BY name");const isNew=applicationId===0&&version===0&&objects.length===0;if(!isNew&&(applicationId!==APPLICATION_ID||version!==SCHEMA_VERSION||JSON.stringify(objects)!==JSON.stringify([{name:"encrypted_intents",type:"table"},{name:"encrypted_intents_namespace",type:"index"}]))){throw error("local_intent_database_incompatible","This file is not a supported participant local intent database.")}database.exec("PRAGMA journal_mode = DELETE; PRAGMA synchronous = EXTRA; PRAGMA temp_store = MEMORY; PRAGMA journal_size_limit = 0;");const db2=database;if(getRow(db2,"PRAGMA journal_mode")?.journal_mode!=="delete"||getRow(db2,"PRAGMA synchronous")?.synchronous!==3||getRow(db2,"PRAGMA locking_mode")?.locking_mode!=="exclusive"){throw error("local_intent_durability_unavailable","The local database could not enable durable exclusive execution.")}db2.transaction(()=>{if(isNew){db2.exec(`CREATE TABLE encrypted_intents (
5
+ record_id TEXT PRIMARY KEY NOT NULL CHECK(length(record_id) BETWEEN 1 AND ${MAX_IDENTIFIER_LENGTH}),
6
+ namespace_id TEXT NOT NULL CHECK(length(namespace_id) BETWEEN 1 AND ${MAX_IDENTIFIER_LENGTH}),
7
+ envelope_version INTEGER NOT NULL CHECK(envelope_version = 1),
8
+ created_at TEXT NOT NULL CHECK(length(created_at) BETWEEN 1 AND 64),
9
+ updated_at TEXT NOT NULL CHECK(length(updated_at) BETWEEN 1 AND 64),
10
+ ciphertext BLOB NOT NULL CHECK(length(ciphertext) BETWEEN 1 AND ${AGENT_LOCAL_INTENT_MAX_CIPHERTEXT_BYTES})
11
+ ) STRICT;
12
+ CREATE INDEX encrypted_intents_namespace ON encrypted_intents(namespace_id);
13
+ PRAGMA application_id = ${APPLICATION_ID}; PRAGMA user_version = ${SCHEMA_VERSION};`)}db2.exec(`PRAGMA user_version = ${SCHEMA_VERSION};`)}).exclusive()}catch(cause){database?.close(true);if(cause instanceof AgentLocalIntentRepositoryError)throw cause;const code=isSqliteBusy(cause)?"local_intent_repository_busy":"local_intent_repository_open_failed";throw error(code,code==="local_intent_repository_busy"?"Another executor owns the participant local intent database.":"The participant local intent database could not be opened.",cause)}const db=database;let closed=false;function requireOpen(){if(closed)throw error("local_intent_repository_closed","The participant local intent repository is closed.")}const stats=(namespaceId)=>getRow(db,"SELECT count(*) AS count, coalesce(sum(length(ciphertext)), 0) AS bytes FROM encrypted_intents WHERE namespace_id = ?",namespaceId);const put=db.transaction((record)=>{const previous=getRow(db,"SELECT namespace_id, created_at, length(ciphertext) AS ciphertext_bytes FROM encrypted_intents WHERE record_id = ?",record.recordId);if(previous&&(previous.namespace_id!==record.namespaceId||previous.created_at!==record.createdAt)){throw error("local_intent_envelope_conflict","A local intent record cannot change its wallet namespace or creation timestamp.")}const totals=stats(record.namespaceId);requireNamespaceBounds(totals.count+(previous?0:1),totals.bytes-(previous?.ciphertext_bytes??0)+record.ciphertext.length);db.run(`INSERT INTO encrypted_intents (record_id, namespace_id, envelope_version, created_at, updated_at, ciphertext)
14
+ VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(record_id) DO UPDATE SET
15
+ updated_at = excluded.updated_at, ciphertext = excluded.ciphertext`,[record.recordId,record.namespaceId,record.envelopeVersion,record.createdAt,record.updatedAt,Uint8Array.from(record.ciphertext)])});const remove=db.transaction((namespaceId)=>{db.run("DELETE FROM encrypted_intents WHERE namespace_id = ?",[namespaceId])});return{repository:{async put(record){requireOpen();validateEnvelope(record);put.exclusive(record)},async list(namespaceId){requireOpen();requireIdentifier(namespaceId);const totals=stats(namespaceId);requireNamespaceBounds(totals.count,totals.bytes);return getRows(db,"SELECT * FROM encrypted_intents WHERE namespace_id = ? ORDER BY created_at, record_id",namespaceId).map((row)=>{if(!(row.ciphertext instanceof Uint8Array)||row.ciphertext.length>AGENT_LOCAL_INTENT_MAX_CIPHERTEXT_BYTES){throw error("local_intent_envelope_invalid","Stored local intent ciphertext has an invalid shape or size.")}const record={envelopeVersion:row.envelope_version,recordId:row.record_id,namespaceId:row.namespace_id,createdAt:row.created_at,updatedAt:row.updated_at,ciphertext:Array.from(row.ciphertext)};validateEnvelope(record);return record})},async deleteNamespace(namespaceId){requireOpen();requireIdentifier(namespaceId);remove.exclusive(namespaceId)}},close(){if(closed)return;db.close(true);closed=true}}}function getRow(database,sql,...bindings){let __stack=[];try{const statement=__using(__stack,database.prepare(sql),0);return statement.get(...bindings)}catch(_catch){var _err=_catch,_hasErr=1}finally{__callDispose(__stack,_err,_hasErr)}}function getRows(database,sql,...bindings){let __stack=[];try{const statement=__using(__stack,database.prepare(sql),0);return statement.all(...bindings)}catch(_catch){var _err=_catch,_hasErr=1}finally{__callDispose(__stack,_err,_hasErr)}}function validateEnvelope(value){if(typeof value!=="object"||value===null||Array.isArray(value)||Reflect.ownKeys(value).length!==ENVELOPE_KEYS.length||ENVELOPE_KEYS.some((key)=>!Object.hasOwn(value,key))||value.envelopeVersion!==1){throw error("local_intent_envelope_invalid","Only a supported local intent ciphertext envelope may be persisted.")}requireIdentifier(value.recordId);requireIdentifier(value.namespaceId);if(!isTimestamp(value.createdAt)||!isTimestamp(value.updatedAt)){throw error("local_intent_envelope_invalid","Local intent timestamps must be canonical ISO timestamps.")}if(!Array.isArray(value.ciphertext)||value.ciphertext.length===0||value.ciphertext.length>AGENT_LOCAL_INTENT_MAX_CIPHERTEXT_BYTES){throw error("local_intent_envelope_invalid","Local intent ciphertext has an invalid shape or size.")}for(let index=0;index<value.ciphertext.length;index++){const byte=value.ciphertext[index];if(!Number.isInteger(byte)||byte<0||byte>255){throw error("local_intent_envelope_invalid","Local intent ciphertext must contain exact integer bytes.")}}}function requireIdentifier(value){if(typeof value!=="string"||!value.trim()||value.length>MAX_IDENTIFIER_LENGTH||value.includes("\x00")){throw error("local_intent_envelope_invalid","A bounded local intent record or namespace identifier is required.")}}function isTimestamp(value){if(typeof value!=="string"||value.length>64||!Number.isFinite(Date.parse(value)))return false;return new Date(value).toISOString()===value}function requireNamespaceBounds(count,bytes){if(count>AGENT_LOCAL_INTENT_MAX_NAMESPACE_RECORDS||bytes>AGENT_LOCAL_INTENT_MAX_NAMESPACE_BYTES){throw error("local_intent_namespace_limit","The participant local intent namespace exceeds its bounded storage budget.")}}function isSqliteBusy(cause){return typeof cause==="object"&&cause!==null&&"code"in cause&&(cause.code==="SQLITE_BUSY"||cause.code==="SQLITE_LOCKED")}function error(code,message,cause){return new AgentLocalIntentRepositoryError(code,message,cause===undefined?undefined:{cause})}function createAgentSponsoredPostExecutor(options){const binding=Object.freeze({...options.binding});const deployment=new URL(options.deployment).origin;if(!supportedParticipantBinding(binding)||!options.store.updateDraft||!options.store.persistEvidence)throw new Error("agent_sponsor_configuration_invalid");const inspector=createAgentWalletConnectionInspector({wallet:options.wallet,binding});const marker3=(id)=>`agent-sponsored-prepared:${id}`;const blocked=(reason)=>({kind:"blocked",reason});let busy=false;function prepared(record){const draft=record.draft;if(draft?.schema!=="mapapp-agent-sponsored-operation-v1")return null;if(record.reference.intentId!==marker3(draft.operationId)||!uuid4(draft.intakeId)||!sameBinding2(draft.connection,binding)||draft.deployment!==deployment||!["prepared","attempt_started"].includes(draft.phase))throw new Error("agent_operation_binding_invalid");return draft}async function exclusive(work){if(busy)return blocked("execution_in_progress");busy=true;try{return await work()}finally{busy=false}}async function admission(caller,input,bapId){const state=await inspector.inspect(caller);if(state.state!=="ready")return state.reason;const gates=await options.gates().catch(()=>null);if(!gates)return"prerequisite_unavailable";if(!gates.stackReady||!gates.intakeReady||!gates.sponsorReady||!gates.bapId)return"prerequisite_blocked";if(bapId!==undefined&&gates.bapId!==bapId)return"identity_binding_changed";if(input.kind==="comment"&&!await options.targetReady(input).catch(()=>false))return"reply_target_unavailable";return null}async function save(draft){await options.store.updateDraft(marker3(draft.operationId),draft)}async function reservation(draft){try{const result=await options.client.authenticatedRequest("sponsorStatus",{version:2,intentId:draft.intakeId},`${deployment}/api/freeposting/u12/status`);const current=result.reservation;if(!current||!uuid4(current.reservationId)||current.intentId!==draft.intakeId||current.kind!=="contribution"||current.bapId!==draft.bapId||current.draftHash!==draft.draftHash||!["reserved","funding","submitted","outcome_unknown","failed","expired"].includes(current.status)||draft.reservationId&&current.reservationId!==draft.reservationId)throw new Error("sponsor_reservation_binding_changed");return current}catch(error2){if(error2 instanceof TransactionIntakeClientError&&error2.code==="sponsor_reservation_not_found"&&error2.status===404)return null;throw error2}}async function reconcilePrepared(draft,records){if(draft.phase==="prepared")return blocked("operation_not_started");try{const matches=records.filter((record)=>record.reference.intentId===draft.intakeId);if(matches.length>1)return{kind:"recovery_required",operationId:draft.operationId,reason:"reference_conflict"};let evidence=matches[0]?.returnedEvidence;const current=await reservation(draft);if(!evidence&&current?.tx){const outcome=normalizePaidPostActionOutcome("agent-wallet-node",current);if(outcome.kind!=="creation_evidence"||outcome.anomaly)return{kind:"recovery_required",operationId:draft.operationId,reason:"sponsor_evidence_invalid"};if(!matches[0])return{kind:"recovery_required",operationId:draft.operationId,reason:"original_reference_missing"};await options.store.persistEvidence(draft.intakeId,outcome.txid,outcome.atomicBeef);evidence={txid:outcome.txid,atomicBeef:Array.from(outcome.atomicBeef),originator:binding.originator};await save({...draft,reservationId:current.reservationId})}if(evidence){if(evidence.originator!==binding.originator)return blocked("evidence_originator_changed");const retained=normalizePaidPostActionOutcome("agent-wallet-node",{txid:evidence.txid,tx:evidence.atomicBeef});if(retained.kind!=="creation_evidence"||retained.anomaly)return{kind:"recovery_required",operationId:draft.operationId,reason:"original_evidence_invalid"};if(current?.tx&&(current.txid!==evidence.txid||JSON.stringify(current.tx)!==JSON.stringify(evidence.atomicBeef))){return{kind:"recovery_required",operationId:draft.operationId,reason:"original_evidence_conflict"}}if(!draft.authorInternalized&&draft.fundingArgs){await options.wallet.internalizeAction({tx:[...evidence.atomicBeef],description:draft.fundingArgs.description,labels:draft.fundingArgs.labels,outputs:(draft.fundingArgs.outputs??[]).map((output,outputIndex)=>({outputIndex,protocol:"basket insertion",insertionRemittance:{basket:output.basket??"default",customInstructions:output.customInstructions,tags:output.tags??[]}}))});await save({...draft,reservationId:current?.reservationId??draft.reservationId,authorInternalized:true})}await options.client.submitEvidence(draft.intakeId,evidence.txid,new Uint8Array(evidence.atomicBeef))}return{kind:"reconciled",operationId:draft.operationId,evidenceResubmitted:!!evidence,intake:await options.client.statusIntent(draft.intakeId),...current?{sponsorState:current.status}:{}}}catch{return{kind:"recovery_required",operationId:draft.operationId,reason:"original_sponsor_or_intake_unavailable"}}}return{prepare(caller,request){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(!uuid4(request.operationId)||!validInput2(request.input))return blocked("invalid_sponsored_input");const input=canonicalInput2(request.input);const existing=(await options.store.list(marker3(request.operationId))).map(prepared).find((value)=>value?.operationId===request.operationId);if(existing&&JSON.stringify(existing.input)!==JSON.stringify(input))return blocked("operation_input_conflict");if(existing&&existing.phase!=="prepared")return blocked("operation_already_started");const denied=await admission(caller,input,existing?.bapId);if(denied)return blocked(denied);let media;try{media=(await inspect(input)).media}catch(error2){return blocked(error2 instanceof AgentMediaError?error2.code:"media_inspection_unavailable")}if(!existing){const gates=await options.gates();if(!gates.bapId)return blocked("prerequisite_blocked");const draft={schema:"mapapp-agent-sponsored-operation-v1",operationId:request.operationId,intakeId:crypto.randomUUID(),connection:binding,deployment,bapId:gates.bapId,input,draftHash:await sponsoredDraftHash(canonicalSponsoredDraft(contribution(input))),phase:"prepared"};const nonce=`agent-sponsored-local:${request.operationId}`;await options.store.persistReference({intentId:marker3(request.operationId),nonce,correlationTag:`intent:${nonce}`,clientIntentId:request.operationId,evidenceFormat:"mapapp_sponsored_v2"},draft)}return{kind:"prepared",operationId:request.operationId,authority:participantAuthorityName(binding),payer:"mapapp",authorSats:0,lockedSats:0,transactionFee:"unknown",...media?{media}:{}}})},execute(caller,operationId,signal){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(signal?.aborted)return blocked("request_cancelled");const retained=await options.store.list(marker3(operationId));const prior=retained.map(prepared).find((value)=>value?.operationId===operationId);const records=prior?.phase==="attempt_started"?[...retained,...await options.store.list(prior.intakeId)]:retained;const draft=records.map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");const denied=await admission(caller,draft.input,draft.bapId);if(denied)return blocked(denied);if(draft.phase==="attempt_started")return reconcilePrepared(draft,records);let thumbnail;try{thumbnail=(await inspect(draft.input)).thumbnail}catch(error2){return blocked(error2 instanceof AgentMediaError?error2.code:"media_inspection_unavailable")}if(signal?.aborted)return blocked("request_cancelled");let durable={...draft,phase:"attempt_started"};await save(durable);try{const intent=await options.client.createIntent(operationId,draft.intakeId,"sponsor");if(intent.intentId!==draft.intakeId)throw new Error("original_intake_binding_changed");await options.store.persistReference({...intent,clientIntentId:operationId,evidenceFormat:"mapapp_sponsored_v2"},contribution(draft.input));if(draft.input.media){if(!options.client.stageRetainedMedia||!options.client.stageThumbnail||!thumbnail)throw new Error("media_staging_unavailable");const staged=await options.client.stageRetainedMedia(intent.intentId,draft.input.media);if(staged.media.sha256!==draft.input.media.sha256||staged.media.byteSize!==draft.input.media.byteSize||staged.media.contentType!==draft.input.media.contentType)throw new Error("media_stage_binding_changed");await options.client.stageThumbnail(intent.intentId,0,draft.input.media.sha256,thumbnail)}if(await admission(caller,draft.input,draft.bapId)||signal?.aborted)throw new Error("pre_dispatch_prerequisite_changed");const claim=await options.client.claimInvocation(intent.intentId,binding.connectionId);if(!claim.claimed||signal?.aborted)throw new Error("original_claim_unavailable");const challenge=await options.challenge(draft.draftHash);const pow=await solveSponsoredPow({challenge,payloadHash:draft.draftHash});if(await admission(caller,draft.input,draft.bapId)||signal?.aborted)throw new Error("pre_reservation_prerequisite_changed");const reserved=await options.client.authenticatedRequest("sponsorReserve",{version:2,intentId:intent.intentId,kind:"contribution",bapId:draft.bapId,draftHash:draft.draftHash,pow},`${deployment}/api/freeposting/u12/reserve`);if(!reserved.reservation||!uuid4(reserved.reservation.reservationId)||reserved.reservation.intentId!==intent.intentId||reserved.reservation.kind!=="contribution"||reserved.reservation.bapId!==draft.bapId||reserved.reservation.draftHash!==draft.draftHash||reserved.reservation.status!=="reserved")throw new Error("reservation_response_invalid");durable={...durable,reservationId:reserved.reservation.reservationId};await save(durable);if(await admission(caller,draft.input,draft.bapId)||signal?.aborted)throw new Error("pre_signing_prerequisite_changed");let fundingCalled=false;const funding={async fund(args){if(fundingCalled)throw new Error("original_funding_already_requested");fundingCalled=true;if(await admission(caller,draft.input,draft.bapId)||signal?.aborted)throw new Error("pre_funding_prerequisite_changed");durable={...durable,fundingRequested:true,fundingArgs:structuredClone(args)};await save(durable);const returned2=await options.client.authenticatedRequest("sponsorFund",{version:2,reservationId:durable.reservationId,args},`${deployment}/api/freeposting/u12/fund`);const outcome2=normalizePaidPostActionOutcome("agent-wallet-node",returned2);if(outcome2.kind!=="creation_evidence")throw new Error("sponsor_funding_evidence_unavailable");await options.store.persistEvidence(intent.intentId,outcome2.txid,outcome2.atomicBeef);if(outcome2.anomaly)throw new Error("sponsor_funding_evidence_invalid");return returned2}};const returned=await options.invoke(contribution(draft.input),funding,intent.correlationTag).catch((cause)=>cause);const outcome=normalizePaidPostActionOutcome("agent-wallet-node",returned);if(outcome.kind!=="creation_evidence"){await options.client.recordOutcome(intent.intentId,outcome.kind==="awaiting_evidence"?"awaiting_evidence":"wallet_outcome_unknown",outcome.kind==="awaiting_evidence"?outcome.txid:undefined).catch(()=>{return});return{kind:"execution",operationId,walletOutcome:outcome.kind,retryAllowed:false,payer:"mapapp",authorSats:0,observedSponsorCost:"unknown"}}const producerResult=returned;if(!producerResult.internalizationError&&!producerResult.error){durable={...durable,authorInternalized:true};await save(durable)}await options.store.persistEvidence(intent.intentId,outcome.txid,outcome.atomicBeef);await options.client.submitEvidence(intent.intentId,outcome.txid,outcome.atomicBeef);return{kind:"execution",operationId,walletOutcome:outcome.kind,retryAllowed:false,payer:"mapapp",authorSats:0,observedSponsorCost:"unknown",txid:outcome.txid,evidenceAnomaly:outcome.anomaly}}catch{return{kind:"recovery_required",operationId,reason:"original_sponsor_operation_interrupted"}}})},status(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");const state=await inspector.inspect(caller);if(state.state!=="ready")return blocked(state.reason);const draft=(await options.store.list(marker3(operationId))).map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="prepared")return{kind:"status",operationId,phase:draft.phase};try{const[intake,current]=await Promise.all([options.client.statusIntent(draft.intakeId),reservation(draft)]);return{kind:"status",operationId,phase:draft.phase,intake,...current?{sponsorState:current.status}:{}}}catch{return{kind:"recovery_required",operationId,reason:"original_sponsor_or_intake_unavailable"}}})},reconcile(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");const state=await inspector.inspect(caller);if(state.state!=="ready")return blocked(state.reason);const retained=await options.store.list(marker3(operationId));const draft=retained.map(prepared).find((value)=>value?.operationId===operationId);const records=draft?.phase==="attempt_started"?[...retained,...await options.store.list(draft.intakeId)]:retained;return draft?reconcilePrepared(draft,records):blocked("operation_not_prepared")})}}}function contribution(input){return input.kind==="post"?{kind:"post",text:input.text,geohash:input.geohash,category:input.category,...input.media?{mediaSha256:input.media.sha256}:{}}:{kind:"comment",text:input.text,parentTxid:input.parentTxid,...input.media?{mediaSha256:input.media.sha256}:{}}}function canonicalInput2(input){const copyMedia=(media2)=>({contentType:media2.contentType,base64:media2.base64,byteSize:media2.byteSize,sha256:media2.sha256});const media=input.media?{media:copyMedia(input.media),thumbnail:copyMedia(input.thumbnail)}:{};return input.kind==="post"?{kind:input.kind,text:input.text,geohash:input.geohash,category:input.category,...media}:{kind:input.kind,text:input.text,parentTxid:input.parentTxid,rootTxid:input.rootTxid,...media}}async function inspect(input){if(!input.media)return{};const media=await inspectAgentMedia(input.media);const thumbnail=await inspectAgentMedia(input.thumbnail);if(thumbnail.summary.contentType!=="image/webp"||thumbnail.summary.width!==64||thumbnail.summary.height!==64)throw new AgentMediaError("thumbnail_dimensions_invalid");return{media:media.summary,thumbnail:thumbnail.media.bytes}}function validInput2(input){try{if(!input||!["post","comment"].includes(input.kind)||typeof input.text!=="string"||new TextDecoder().decode(new TextEncoder().encode(input.text))!==input.text)return false;const allowed=input.kind==="post"?["kind","text","geohash","category","media","thumbnail"]:["kind","text","parentTxid","rootTxid","media","thumbnail"];if(Object.keys(input).some((key)=>!allowed.includes(key)))return false;if(input.media?!validAgentMediaInput(input.media)||!validAgentMediaInput(input.thumbnail)||input.thumbnail.contentType!=="image/webp"||input.thumbnail.byteSize>64*1024:input.thumbnail!==undefined)return false;if(input.kind==="post"&&!MAPAPP_CATEGORY_TOKENS.includes(input.category))return false;if(input.kind==="comment"&&(!/^[0-9a-f]{64}$/.test(input.parentTxid)||!/^[0-9a-f]{64}$/.test(input.rootTxid)))return false;assertSponsoredDraft(contribution(input));return true}catch{return false}}function uuid4(value){return typeof value==="string"&&/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(value)}function sameBinding2(left,right){return!!left&&Object.keys(right).every((key)=>left[key]===right[key])}import{applyBapAip,BSOCIAL_BASKET,executeTrackedAction,publishIdentity}from"@1sat/actions";import{B,BitCom,MAP}from"@1sat/templates";import{OP,Script}from"@bsv/sdk";async function executeSponsoredContribution(context,draft,fundingClient,privateTag){const signed=await applyBapAip(context,prepareSponsoredContributionScript(draft));return executeWithEvidenceRetention(context,assembleSponsoredContributionAction(draft,signed.toHex(),privateTag),fundingClient)}function prepareSponsoredContributionScript(draft){const textProtocol=oneProtocol(B.text(draft.text,"text/plain"));const mapData=draft.kind==="post"?{app:"mapapp",type:"post",context:"geohash",contextValue:draft.geohash,category:draft.category,...draft.mediaSha256?{mediaSha256:draft.mediaSha256.toLowerCase()}:{}}:{app:"mapapp",type:"post",context:"tx",contextValue:draft.parentTxid.toLowerCase(),...draft.mediaSha256?{mediaSha256:draft.mediaSha256.toLowerCase()}:{}};const mapProtocol=oneProtocol(MAP.set(mapData));const bareUnsigned=new BitCom([{...textProtocol,pos:0},{...mapProtocol,pos:1}]).lock();return new Script([{op:OP.OP_FALSE},...bareUnsigned.chunks])}function assembleSponsoredContributionAction(draft,signedScriptHex,privateTag){return{description:draft.kind==="post"?"MapApp sponsored post":"MapApp sponsored reply",outputs:[{lockingScript:signedScriptHex,satoshis:0,outputDescription:draft.kind==="post"?"MapApp sponsored post":"MapApp sponsored reply",basket:BSOCIAL_BASKET,tags:[privateTag,"app:mapapp","type:post",`sponsor-kind:${draft.kind}`]}],options:{acceptDelayedBroadcast:false,randomizeOutputs:false}}}async function executeSponsoredIdentity(context,fundingClient){const retained={value:null};const provider={async fund(args){retained.value=await fundingClient.fund(args);return retained.value}};try{const result=await publishIdentity.execute(context,{fundingProvider:provider});if(result.error)return{error:result.error,txid:result.txid,tx:retained.value?Array.from(retained.value.tx):undefined,bapId:result.bapId};return{txid:result.txid,tx:retained.value?Array.from(retained.value.tx):undefined,bapId:result.bapId}}catch(cause){if(retained.value){return{txid:retained.value.txid,tx:Array.from(retained.value.tx),internalizationError:cause instanceof Error?cause.message:"Author wallet internalization failed."}}return{error:cause instanceof Error?cause.message:"Sponsored identity action failed."}}}async function executeWithEvidenceRetention(context,args,fundingClient){const retained={value:null};const provider={async fund(actionArgs){retained.value=await fundingClient.fund(actionArgs);return retained.value}};try{const result=await executeTrackedAction(context.wallet,args,provider);return{txid:result.txid??retained.value?.txid,tx:retained.value?Array.from(retained.value.tx):result.tx,...result.error?{error:result.error}:{}}}catch(cause){if(retained.value){return{txid:retained.value.txid,tx:Array.from(retained.value.tx),internalizationError:cause instanceof Error?cause.message:"Author wallet internalization failed."}}return{error:cause instanceof Error?cause.message:"Sponsored action failed before evidence returned."}}}function oneProtocol(script){const decoded=BitCom.decode(script);if(!decoded||decoded.protocols.length!==1)throw new Error("Protocol template did not produce one BitCom section.");return decoded.protocols[0]}var schema3="mapapp-agent-social-operation-v1";var marker3=(id)=>`agent-social-prepared:${id}`;var uuid5=(value)=>/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/u.test(value);var txid=(value)=>typeof value==="string"&&/^[0-9a-f]{64}$/u.test(value);function createAgentSocialActionExecutor(options){const binding=Object.freeze({...options.binding}),deployment=new URL(options.deployment).origin;if(!participantProfileMatches(binding,options.proofProfile)||!options.store.updateDraft||!options.store.persistEvidence)throw new Error("agent_executor_configuration_invalid");const inspector=createAgentWalletConnectionInspector({wallet:options.wallet,binding});const blocked=(reason)=>({kind:"blocked",reason});let busy=false;async function exclusive(work){if(busy)return blocked("execution_in_progress");busy=true;try{return await work()}finally{busy=false}}function prepared(record){const draft=record.draft;if(draft?.schema!==schema3)return null;if(record.reference.intentId!==marker3(draft.operationId)||!uuid5(draft.intakeId)||draft.deployment!==deployment||!sameBinding3(draft.connection,binding)||!["prepared","attempt_started"].includes(draft.phase)||!validInput3(draft.input))throw new Error("agent_operation_binding_invalid");return draft}async function admission(caller,input){const inspected=await inspector.inspect(caller);if(inspected.state!=="ready")return inspected.reason;const gates=await options.gates().catch(()=>null);if(!gates||!gates.stackReady||!gates.intakeReady||!gates.identityReady)return"prerequisite_blocked";if(!await options.targetReady(input).catch(()=>false))return"social_target_unavailable";return null}async function reconcilePrepared(draft,records){if(draft.phase!=="attempt_started")return blocked("operation_not_started");const matches=records.filter((record)=>record.reference.intentId===draft.intakeId);if(matches.length>1)return{kind:"recovery_required",operationId:draft.operationId,reason:"reference_conflict"};try{let evidence=matches[0]?.returnedEvidence;if(!evidence&&matches[0]){const recovered=await recoverPaidPostEvidence({wallet:options.wallet,correlationTag:matches[0].reference.correlationTag,evidenceFormat:"mapapp_social_v2"});if(recovered.kind==="recovered"){await options.store.persistEvidence(draft.intakeId,recovered.txid,recovered.atomicBeef);evidence={txid:recovered.txid,atomicBeef:Array.from(recovered.atomicBeef),originator:binding.originator}}}if(evidence){if(evidence.originator!==binding.originator)return blocked("evidence_originator_changed");await options.client.submitEvidence(draft.intakeId,evidence.txid,new Uint8Array(evidence.atomicBeef))}return{kind:"reconciled",operationId:draft.operationId,intake:await options.client.statusIntent(draft.intakeId),evidenceResubmitted:!!evidence}}catch{return{kind:"recovery_required",operationId:draft.operationId,reason:"original_intake_unavailable"}}}return{prepare(caller,request){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(!uuid5(request.operationId)||!validInput3(request.input))return blocked("invalid_social_input");const input=canonicalInput3(request.input),records=await options.store.list();const existing=records.map(prepared).find((draft)=>draft?.operationId===request.operationId);if(existing&&JSON.stringify(existing.input)!==JSON.stringify(input))return blocked("operation_input_conflict");if(existing?.phase==="attempt_started")return blocked("operation_already_started");if(!existing&&records.some((record)=>record.reference.clientIntentId===request.operationId))return blocked("operation_input_conflict");const denied=await admission(caller,input);if(denied)return blocked(denied);if(!existing){const draft={schema:schema3,operationId:request.operationId,intakeId:crypto.randomUUID(),connection:binding,deployment,input,phase:"prepared"};const nonce=`agent-local:${request.operationId}`;await options.store.persistReference({intentId:marker3(request.operationId),clientIntentId:request.operationId,nonce,correlationTag:`intent:${nonce}`,evidenceFormat:"mapapp_social_v2"},draft)}return{kind:"prepared",operationId:request.operationId,authority:participantAuthorityName(binding)}})},execute(caller,operationId,signal){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);if(caller!==binding.callerId)return blocked("caller_mismatch");if(signal?.aborted)return blocked("request_cancelled");const records=await options.store.list(),draft=records.map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="attempt_started")return reconcilePrepared(draft,records);const denied=await admission(caller,draft.input);if(denied)return blocked(denied);for(const record of records){const prior=prepared(record);if(!prior||prior.phase!=="attempt_started"||!sameToggleTarget(prior.input,draft.input))continue;const status=await options.client.statusIntent(prior.intakeId).catch(()=>null);if(status?.observations?.proof!=="verified"){return{kind:"blocked",reason:"social_confirmation_pending",blockingOperationId:prior.operationId}}}if(draft.input.kind!=="reply"&&options.targetState){const current=await options.targetState(draft.input).catch(()=>null);if(!current)return blocked("social_state_unavailable");if(current.pendingTxid)return blocked("social_confirmation_pending");if(current.active===["like","follow"].includes(draft.input.kind))return blocked("social_state_already_current")}if(signal?.aborted)return blocked("request_cancelled");await options.store.updateDraft(marker3(operationId),{...draft,phase:"attempt_started"});try{const result=await executePaidPostAction({provider:"agent-wallet-node",providerAttested:false,agentProofProfile:options.proofProfile,client:options.client,clientIntentId:operationId,intentId:draft.intakeId,tabId:binding.connectionId,localIntentStore:options.store,draft:{schema:"mapapp-agent-social-intake-v1",operationId,input:draft.input},evidenceFormat:"mapapp_social_v2",actionKind:actionKind(draft.input),invoke:async(metadata)=>{if(signal?.aborted||await admission(caller,draft.input))throw new Error("agent_pre_dispatch_binding_or_prerequisite_changed");if(draft.input.kind!=="reply"&&options.targetState){const current=await options.targetState(draft.input);if(current.pendingTxid||current.active===["like","follow"].includes(draft.input.kind))throw new Error("agent_pre_dispatch_social_state_changed")}return options.invoke(draft.input,metadata.outputTags[0])}});return{kind:"execution",operationId,result}}catch{return{kind:"recovery_required",operationId,reason:"original_operation_interrupted"}}})},status(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);const inspected=await inspector.inspect(caller);if(inspected.state!=="ready")return blocked(inspected.reason);const draft=(await options.store.list(marker3(operationId))).map(prepared).find((value)=>value?.operationId===operationId);if(!draft)return blocked("operation_not_prepared");if(draft.phase==="prepared")return{kind:"status",operationId,phase:draft.phase};try{return{kind:"status",operationId,phase:draft.phase,intake:await options.client.statusIntent(draft.intakeId)}}catch{return{kind:"recovery_required",operationId,reason:"original_intake_unavailable"}}})},reconcile(caller,operationId){return exclusive(async()=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return blocked(connection.reason);const inspected=await inspector.inspect(caller);if(inspected.state!=="ready")return blocked(inspected.reason);const retained=await options.store.list(marker3(operationId)),draft=retained.map(prepared).find((value)=>value?.operationId===operationId);const records=draft?.phase==="attempt_started"?[...retained,...await options.store.list(draft.intakeId)]:retained;return draft?reconcilePrepared(draft,records):blocked("operation_not_prepared")})}}}function sameBinding3(left,right){return!!left&&Object.keys(right).every((key)=>left[key]===right[key])}function actionKind(input){return input.kind==="reply"?"comment":input.kind==="follow"||input.kind==="unfollow"?"follow":"reaction"}function canonicalInput3(input){if(input.kind==="reply")return{kind:"reply",parentTxid:input.parentTxid,rootTxid:input.rootTxid,text:input.text};return"targetTxid"in input?{kind:input.kind,targetTxid:input.targetTxid}:{kind:input.kind,targetBapId:input.targetBapId}}function validInput3(input){if(!input||typeof input!=="object")return false;const keys=Object.keys(input);if(input.kind==="reply")return keys.every((key)=>["kind","parentTxid","rootTxid","text"].includes(key))&&txid(input.parentTxid)&&txid(input.rootTxid)&&typeof input.text==="string"&&!!input.text.trim()&&new TextEncoder().encode(input.text).length<=16384&&new TextDecoder().decode(new TextEncoder().encode(input.text))===input.text;if(input.kind==="like"||input.kind==="unlike")return keys.every((key)=>["kind","targetTxid"].includes(key))&&txid(input.targetTxid);if(input.kind==="follow"||input.kind==="unfollow")return keys.every((key)=>["kind","targetBapId"].includes(key))&&typeof input.targetBapId==="string"&&/^[1-9A-HJ-NP-Za-km-z]{20,64}$/u.test(input.targetBapId);return false}function sameToggleTarget(left,right){if("targetTxid"in left&&"targetTxid"in right)return left.targetTxid===right.targetTxid;if("targetBapId"in left&&"targetBapId"in right)return left.targetBapId===right.targetBapId;return false}import{applyBapAip as applyBapAip2,BSOCIAL_BASKET as BSOCIAL_BASKET2,executeTrackedAction as executeTrackedAction2,P1SAT_PROTOCOL}from"@1sat/actions";import{AIP,B as B2,BitCom as BitCom2,Lock,MAP as MAP2}from"@1sat/templates";import{OP as OP2,PublicKey,Script as Script2,Utils as Utils3}from"@bsv/sdk";import{LOCK_BASKET}from"@1sat/types";async function executeSocialV2Action(context,input,privateTag,execute){return executeSocialV2Actions(context,[input],privateTag,execute)}async function executeSocialV2Actions(context,inputs,privateTag,execute){assertSocialInputs(inputs);const signedScripts=[];let lockPublicKey;for(const input of inputs){const unsigned=prepareSocialV2Script(input);const signed=await applyBapAip2(context,unsigned,"identity-1");signedScripts.push(signed.toHex());if(input.kind==="freshen"&&!lockPublicKey){lockPublicKey=(await context.wallet.getPublicKey({protocolID:P1SAT_PROTOCOL,keyID:"lock",counterparty:"self",forSelf:true})).publicKey}}const args=assembleSocialV2Action(inputs,privateTag,signedScripts,{lockPublicKey,...execute?{labels:[`mapapp correlation ${privateTag}`]}:{}});const result=await(execute??((args2)=>executeTrackedAction2(context.wallet,args2)))(args);if(result.error==="broadcast-success"){const recovered=await recoverPaidPostEvidence({wallet:context.wallet,correlationTag:privateTag,evidenceFormat:"mapapp_post_v2"});if(recovered.kind==="recovered"){return{txid:recovered.txid,tx:Array.from(recovered.atomicBeef)}}}return{txid:result.txid,tx:result.tx,error:result.error}}function prepareSocialV2Script(input){const bareUnsigned=new BitCom2([..."text"in input?[single(B2.text(input.text,"text/plain"),0)]:[],single(MAP2.set(mapFields(input)),"text"in input?1:0)]).lock();return new Script2([{op:OP2.OP_FALSE},...bareUnsigned.chunks])}function assembleSocialV2Action(inputs,privateTag,signedScripts,options={}){assertSocialInputs(inputs);if(signedScripts.length!==inputs.length)throw new Error("One signed script per social action is required.");const outputs=[];for(const[index,input]of inputs.entries()){outputs.push({lockingScript:signedScripts[index],satoshis:0,outputDescription:description(input),basket:BSOCIAL_BASKET2,tags:[privateTag,"app:mapapp",`type:${mapFields(input).type}`,...localTargetTags(input)]});if(input.kind==="reply"&&input.media){outputs.push({lockingScript:standalone(single(B2.binary(input.media.bytes,input.media.contentType),0)).toHex(),satoshis:0,outputDescription:`MapApp reply ${input.media.contentType}`})}if(input.kind==="freshen"){if(!options.lockPublicKey)throw new Error("Freshen requires the participant lock public key.");const lockAddress=PublicKey.fromString(options.lockPublicKey).toAddress();outputs.push({lockingScript:Lock.lock(lockAddress,input.unlockHeight).toHex(),satoshis:input.satoshis,outputDescription:`MapApp freshness extension until block ${input.unlockHeight}`,basket:LOCK_BASKET,tags:["app:mapapp","type:freshness","kind:extension",`post:${input.targetTxid}`,`until:${input.unlockHeight}`],customInstructions:JSON.stringify({protocolID:P1SAT_PROTOCOL,keyID:"lock",app:"mapapp",kind:"extension-freshness"})})}}return{description:inputs.length===1?description(inputs[0]):`MapApp ${inputs.length}-action social batch`,...options.labels?{labels:options.labels}:{},outputs,options:{acceptDelayedBroadcast:false,randomizeOutputs:false}}}function assertSocialInputs(inputs){if(!inputs.length)throw new Error("At least one MapApp social action is required.");if(inputs.length>1&&inputs.some((input)=>input.kind==="reply")){throw new Error("Replies remain one-per-transaction because BSocial tx references cannot address sibling reply outputs.")}}function standalone(protocol){const bare=new BitCom2([protocol]).lock();return new Script2([{op:OP2.OP_FALSE},...bare.chunks])}function localTargetTags(input){switch(input.kind){case"like":case"unlike":return[`tx:${input.targetTxid}`];case"follow":case"unfollow":return[`bapId:${input.targetBapId}`.trim().toLowerCase()];default:return[]}}function mapFields(input){switch(input.kind){case"reply":return{app:"mapapp",type:"post",context:"tx",contextValue:input.parentTxid};case"like":case"unlike":return{app:"mapapp",type:input.kind,tx:input.targetTxid};case"follow":case"unfollow":return{app:"mapapp",type:input.kind,bapId:input.targetBapId};case"freshen":return{app:"mapapp",type:"freshen",tx:input.targetTxid}}}function single(script,pos){const decoded=BitCom2.decode(script);if(!decoded||decoded.protocols.length!==1){throw new Error("Public protocol template did not produce one BitCom section.")}return{...decoded.protocols[0],pos}}function description(input){return`MapApp ${input.kind==="reply"?"Post v2 reply":input.kind}`}import{join}from"path";import{resolveBapId,signWithBap}from"@1sat/actions";import{Utils as Utils4}from"@bsv/sdk";import{createAuthProof as createAuthProof2}from"@bsv/auth";var MESSAGING_CONTROLLER_PROOF_VERSION=1;var MESSAGING_CONTROLLER_DOMAIN=`mapapp-messaging-control-v1
16
+ `;var MESSAGING_DIRECTORY_AUTH_PROTOCOL=[2,"mapapp messaging directory"];var MESSAGING_DIRECTORY_AUTH_ACTIONS={register:"register",rotate:"rotate",revoke:"revoke",selfRead:"authenticated self read"};function messagingControllerMessage(rawMutationBody){return MESSAGING_CONTROLLER_DOMAIN+new TextDecoder("utf-8",{fatal:true}).decode(rawMutationBody)}class MessagingDirectoryClientError extends Error{status;code;constructor(status,code,message){super(message);this.status=status;this.code=code;this.name="MessagingDirectoryClientError"}}async function fetchMessagingDirectoryConfig(fetcher=globalThis.fetch,baseUrl="/api"){const response=await fetcher(`${baseUrl.replace(/\/$/,"")}/messaging/auth-config`,{headers:{accept:"application/json"}});let value;try{value=await response.json()}catch{throw new MessagingDirectoryClientError(response.status,"messaging_directory_invalid_response","Messaging directory returned an invalid configuration response.")}if(!response.ok||!value||typeof value!=="object"||Array.isArray(value))throw new MessagingDirectoryClientError(response.status,"messaging_directory_invalid_response","Messaging directory returned an invalid configuration response.");const record=value;const keys=["enabled","developmentCharacterization","messageBoxHost","historyHost","messageBox","deployment","canonicalOrigin","network","proof"];if(Object.keys(record).length!==keys.length||keys.some((key)=>!(key in record)))throw new MessagingDirectoryClientError(response.status,"messaging_directory_invalid_response","Messaging directory returned an invalid configuration response.");try{assertConfig(record)}catch{throw new MessagingDirectoryClientError(503,"messaging_directory_contract_mismatch","Messaging directory configuration does not match MapApp's pinned contract.")}return record}function createMessagingDirectoryClient(options){const fetcher=options.fetch??globalThis.fetch;const baseUrl=(options.baseUrl??"/api").replace(/\/$/,"");if(options.originator!==undefined)assertOriginator(options.originator);assertConfig(options.config);const bapId=options.bapId??options.profile?.bapId??"";if(bapId)assertBapId(bapId);if(options.profile&&options.profile.bapId!==bapId)throw new MessagingDirectoryClientError(400,"messaging_bap_mismatch","Profile evidence does not match the selected BAP identity.");return{register:(walletIdentityKey)=>mutate("register",walletIdentityKey),rotate:(walletIdentityKey)=>mutate("rotate",walletIdentityKey),revoke:(walletIdentityKey)=>mutate("revoke",walletIdentityKey),async profileSnapshot(bapId2){assertBapId(bapId2);return await profileSnapshotResult(await fetcher(`${baseUrl}/messaging/${encodeURIComponent(bapId2)}/profile-snapshot`,{headers:{accept:"application/json"}}),bapId2)},async lookup(bapId2){assertBapId(bapId2);return await get(`${baseUrl}/messaging/${encodeURIComponent(bapId2)}`,{bapId:bapId2})},async reverseLookup(walletIdentityKey){try{assertCompressedIdentityKey(walletIdentityKey,"wallet identity key")}catch{throw new MessagingDirectoryClientError(400,"messaging_identity_key_invalid","Wallet identity key is invalid.")}return await get(`${baseUrl}/messaging/by-identity/${encodeURIComponent(walletIdentityKey)}`,{walletIdentityKey})}};async function mutate(operation,walletIdentityKey){if(!bapId)throw new MessagingDirectoryClientError(400,"messaging_bap_id_invalid","BAP ID is required for messaging directory mutations.");const mutation={version:MESSAGING_IDENTITY_MUTATION_VERSION,operation,bapId,walletIdentityKey,messageBoxHost:options.config.messageBoxHost,deployment:options.config.deployment,canonicalOrigin:options.config.canonicalOrigin,network:options.config.network,expectedProfileOutpoint:options.profile?.expectedProfileOutpoint??null,expectedEvidenceHash:options.profile?.expectedEvidenceHash??null,acceptedSequence:options.profile?.acceptedSequence??null,projectionVersion:options.profile?.projectionVersion??null,clientOperationId:(options.clientOperationId??defaultOperationId)()};const rawBody=serializeMessagingIdentityMutationV1(mutation);const controller=await options.signWithBap(options.bapContext,messagingControllerMessage(rawBody));if(controller.bapId!==mutation.bapId)throw new MessagingDirectoryClientError(400,"messaging_bap_mismatch","The BAP signer does not match the selected profile.");const controllerProof={version:MESSAGING_CONTROLLER_PROOF_VERSION,signerAddress:controller.address,publicKey:controller.publicKey,signature:controller.signature};const proof=await createAuthProof2({wallet:options.wallet,counterparty:options.config.proof.verifierIdentityKey,action:options.config.proof.actions[operation],body:rawBody,protocol:options.config.proof.protocol,windowMs:options.config.proof.proofLifetimeMs});const path=operation==="register"?`/profiles/${encodeURIComponent(mutation.bapId)}/messaging`:`/profiles/${encodeURIComponent(mutation.bapId)}/messaging/${operation}`;const response=await fetcher(`${baseUrl}${path}`,{method:operation==="register"?"PUT":"POST",headers:{"content-type":"application/json",accept:"application/json","x-mapapp-brc138-proof":encode(proof),"x-mapapp-messaging-controller-proof":encode(controllerProof)},body:rawBody});return await mutationResult(response,options.config,mutation)}async function get(path,expected){return await lookupResult(await fetcher(path,{headers:{accept:"application/json"}}),options.config,expected)}}function assertConfig(config){if(typeof config.developmentCharacterization!=="boolean")throw new MessagingDirectoryClientError(503,"messaging_directory_contract_mismatch","Messaging directory configuration does not match MapApp's pinned contract.");if(!config.enabled||!config.proof.enabled||!config.proof.verifierIdentityKey)throw new MessagingDirectoryClientError(503,"messaging_directory_disabled","The trusted messaging directory is disabled.");if(config.messageBox!=="mapapp-messaging-v1"||JSON.stringify(config.proof.protocol)!==JSON.stringify(MESSAGING_DIRECTORY_AUTH_PROTOCOL)||config.proof.actions.register!==MESSAGING_DIRECTORY_AUTH_ACTIONS.register||config.proof.actions.rotate!==MESSAGING_DIRECTORY_AUTH_ACTIONS.rotate||config.proof.actions.revoke!==MESSAGING_DIRECTORY_AUTH_ACTIONS.revoke)throw new MessagingDirectoryClientError(503,"messaging_directory_contract_mismatch","Messaging directory configuration does not match MapApp's pinned contract.");try{assertHttpsOrigin(config.historyHost);assertCompressedIdentityKey(config.proof.verifierIdentityKey,"messaging verifier identity key");assertScopedBinding({version:"mapapp-messaging-identity-v1",bapId:"config",walletIdentityKey:config.proof.verifierIdentityKey,messageBoxHost:config.messageBoxHost,deployment:config.deployment,canonicalOrigin:config.canonicalOrigin,network:config.network,expectedProfileOutpoint:null,expectedEvidenceHash:null,acceptedSequence:null,projectionVersion:null},config)}catch{throw new MessagingDirectoryClientError(503,"messaging_directory_contract_mismatch","Messaging directory configuration is malformed.")}}function assertHttpsOrigin(value){const url=new URL(value);if(url.protocol!=="https:"||url.origin!==value||url.pathname!=="/"||url.search||url.hash||url.username||url.password)throw new TypeError("Messaging history host must be an HTTPS origin.")}function assertOriginator(originator){if(!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/.test(originator))throw new TypeError("Messaging originator must be a canonical FQDN.")}async function mutationResult(response,config,expected){const body=await responseBody(response);if(expected.operation==="register"){if(body.state!=="registered"&&body.state!=="idempotent"||!hasExactKeys(body,["ok","state","binding","keyVersion","updatedAt"]))invalidResponse()}else if(expected.operation==="rotate"){if(body.state!=="rotated"||!hasExactKeys(body,["ok","state","binding","keyVersion","updatedAt"]))invalidResponse()}else{if(body.state!=="revoked"||!hasExactKeys(body,["ok","state"]))invalidResponse();return{state:"revoked",binding:null}}const binding=parseBinding(body,config);if(binding.binding.bapId!==expected.bapId||binding.binding.walletIdentityKey!==expected.walletIdentityKey)invalidResponse();return{state:body.state,binding}}async function lookupResult(response,config,expected){const body=await responseBody(response);if(!hasExactKeys(body,["ok","binding","keyVersion","updatedAt"]))invalidResponse();const binding=parseBinding(body,config);if(expected.bapId&&binding.binding.bapId!==expected.bapId||expected.walletIdentityKey&&binding.binding.walletIdentityKey!==expected.walletIdentityKey)invalidResponse();return binding}async function profileSnapshotResult(response,bapId){const body=await responseBody(response);if(!hasExactKeys(body,["ok","profile"]))invalidResponse();const profile=body.profile;if(profile===null)return null;if(!profile||typeof profile!=="object"||Array.isArray(profile))invalidResponse();const record=profile;if(Object.keys(record).length!==5||record.bapId!==bapId||typeof record.expectedProfileOutpoint!=="string"||!/^[0-9a-f]{64}\.\d+$/.test(record.expectedProfileOutpoint)||typeof record.expectedEvidenceHash!=="string"||!/^[0-9a-f]{64}$/.test(record.expectedEvidenceHash)||!Number.isSafeInteger(record.acceptedSequence)||record.acceptedSequence<1||!Number.isSafeInteger(record.projectionVersion)||record.projectionVersion<0)invalidResponse();return record}async function responseBody(response){let body;try{body=await response.json()}catch{throw new MessagingDirectoryClientError(response.status,"messaging_directory_invalid_response","Messaging directory returned an invalid response.")}if(!body||typeof body!=="object"||Array.isArray(body))invalidResponse();const record=body;if(!response.ok)throw new MessagingDirectoryClientError(response.status,typeof record.code==="string"?record.code:"messaging_directory_failed",typeof record.message==="string"?record.message:"Messaging directory request failed.");if(record.ok!==true)invalidResponse();return record}function parseBinding(body,config){if(!Number.isSafeInteger(body.keyVersion)||body.keyVersion<1||typeof body.updatedAt!=="string"||!isCanonicalIsoTimestamp(body.updatedAt))invalidResponse();let binding;try{binding=assertMessagingIdentityV1(body.binding);assertScopedBinding(binding,config)}catch{invalidResponse()}return{binding,keyVersion:body.keyVersion,updatedAt:body.updatedAt}}function assertScopedBinding(binding,config){if(binding.messageBoxHost!==config.messageBoxHost||binding.deployment!==config.deployment||binding.canonicalOrigin!==config.canonicalOrigin||binding.network!==config.network)throw new TypeError("Messaging directory binding scope mismatch.")}function assertBapId(bapId){if(typeof bapId!=="string"||bapId.trim()!==bapId||bapId.length===0||bapId.length>128||hasControlCharacter(bapId))throw new MessagingDirectoryClientError(400,"messaging_bap_id_invalid","BAP ID is invalid.")}function hasControlCharacter(value){for(const character of value){const code=character.charCodeAt(0);if(code<=31||code===127)return true}return false}function hasExactKeys(value,keys){const actual=Object.keys(value);return actual.length===keys.length&&keys.every((key)=>Object.hasOwn(value,key))}function isCanonicalIsoTimestamp(value){if(!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))return false;const parsed=new Date(value);return Number.isFinite(parsed.getTime())&&parsed.toISOString()===value}function invalidResponse(){throw new MessagingDirectoryClientError(502,"messaging_directory_invalid_response","Messaging directory returned an invalid response.")}function encode(value){const bytes=new TextEncoder().encode(JSON.stringify(value));let text="";for(let i=0;i<bytes.length;i+=32768)text+=String.fromCharCode(...bytes.subarray(i,i+32768));return btoa(text).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function defaultOperationId(){const id=globalThis.crypto?.randomUUID?.();if(!id)throw new MessagingDirectoryClientError(503,"messaging_secure_random_unavailable","Secure randomness is required for a messaging directory operation.");return id}var MESSAGING_POLL_INTERVAL_MS=5000;var MESSAGING_PRIVACY_DISCLOSURE=Object.freeze(["Messaging and storage are end-to-end encrypted."]);class MessagingCoordinatorError extends Error{code;constructor(code,message,options){super(message,options);this.code=code;this.name="MessagingCoordinatorError"}}class MessagingCoordinator{options;partition=null;identityKey=null;interval=null;inFlightPoll=null;inFlightPollEpoch=null;inFlightPollSilent=false;inFlightPollAbort=null;sessionEpoch=0;lastPollingFailure=null;setupState="ineligible";activeSends=new Map;now;messageId;constructor(options){this.options=options;if(options.originator!==undefined)assertOriginator2(options.originator);this.now=options.now??(()=>new Date);this.messageId=options.messageId??defaultMessageId}async start(readOptions={}){this.assertReceipt();const startBoundary=this.sessionEpoch;const wallet=readOptions.silent?this.options.silentWallet:this.options.wallet;const ports=readOptions.silent?this.options.silentReadPorts:this.options;if(!wallet||!ports)throw new MessagingCoordinatorError("silent_access_unavailable","Silent messaging access is unavailable.");const identity=await wallet.getPublicKey({identityKey:true},this.options.originator);assertCompressedIdentityKey(identity.publicKey,"connected wallet identity key");this.assertEpoch(startBoundary);if(this.identityKey||this.partition)this.disconnect();const epoch=++this.sessionEpoch;this.identityKey=identity.publicKey;this.partition=null;const partition=await ports.history.open(identity.publicKey);if(this.sessionEpoch!==epoch||this.identityKey?.toLowerCase()!==identity.publicKey.toLowerCase()){ports.history.clearSessionCache(partition.identityFingerprint);throw new MessagingCoordinatorError("session_changed","Messaging wallet session changed while opening messaging.")}this.partition=partition;const session={...ports,identityKey:identity.publicKey,partition,epoch};const setup=await this.readSetupState(identity.publicKey,session);this.assertCurrentSession(session);this.setupState=setup;if(this.setupState==="registered"&&this.options.automaticPolling!==false)this.startPolling();const read=await ports.history.read(partition);this.assertCurrentSession(session);if(this.setupState==="registered"&&this.options.automaticPolling!==false){this.poll(readOptions).catch((cause)=>{if(this.isCurrentSession(session))this.lastPollingFailure=asError(cause)})}return{identityKey:this.identityKey,snapshot:read.snapshot,setup:this.setupState}}async state(readOptions={}){const session=this.requireSession(readOptions);const read=await session.history.read(session.partition);this.assertCurrentSession(session);return{identityKey:session.identityKey,snapshot:read.snapshot,setup:this.setupState}}async refreshSetup(){const session=this.requireSession();const setup=await this.readSetupState(session.identityKey,session);this.assertCurrentSession(session);this.setupState=setup;return this.setupState}async registerMessaging(){const session=this.requireSession();if(this.setupState==="rotation-required")throw new MessagingCoordinatorError("rotation_required","This profile already has a different messaging key. Rotate it explicitly instead of registering a second key.");if(!this.options.directory.register)throw new MessagingCoordinatorError("registration_unavailable","Messaging registration is not configured.");this.assertCurrentSession(session);const result=await this.options.directory.register(session.identityKey);this.assertCurrentSession(session);const setup=registrationResultMatches(result,session.identityKey)?"registered":await this.readSetupState(session.identityKey,session);this.assertCurrentSession(session);if(setup!=="registered")throw new MessagingCoordinatorError("registration_unconfirmed","Messaging registration was not confirmed for the connected wallet identity.");this.setupState=setup;return setup}async rotateMessaging(){const session=this.requireSession();if(!this.options.directory.rotate)throw new MessagingCoordinatorError("rotation_unavailable","Messaging rotation is not configured.");this.assertCurrentSession(session);const result=await this.options.directory.rotate(session.identityKey);this.assertCurrentSession(session);const setup=registrationResultMatches(result,session.identityKey)?"registered":await this.readSetupState(session.identityKey,session);this.assertCurrentSession(session);if(setup!=="registered")throw new MessagingCoordinatorError("rotation_unconfirmed","Messaging rotation was not confirmed for the connected wallet identity.");this.setupState=setup;if(this.options.automaticPolling!==false)this.startPolling();return setup}async revokeMessaging(){const session=this.requireSession();if(!this.options.directory.revoke)throw new MessagingCoordinatorError("revocation_unavailable","Messaging revocation is not configured.");this.assertCurrentSession(session);await this.options.directory.revoke(session.identityKey);this.assertCurrentSession(session);this.stopPolling();this.setupState="eligible-unregistered";return this.setupState}async poll(readOptions={}){const session=this.requireSession(readOptions);if(this.inFlightPoll&&this.inFlightPollEpoch===session.epoch){if(!readOptions.silent&&this.inFlightPollSilent){await this.inFlightPoll.catch(()=>{return});this.assertCurrentSession(session);return await this.poll(readOptions)}return await this.inFlightPoll}const abort=session.durableHistory?new AbortController:null;const pending=this.pollOnce(session,abort?.signal,readOptions);this.inFlightPoll=pending;this.inFlightPollEpoch=session.epoch;this.inFlightPollSilent=readOptions.silent===true;this.inFlightPollAbort=abort;try{return await pending}finally{if(this.inFlightPoll===pending){this.inFlightPoll=null;this.inFlightPollEpoch=null;this.inFlightPollAbort=null;if(this.isCurrentSession(session))this.options.onPoll?.(readOptions)}}}async waitForIdle(){const pending=this.inFlightPoll;if(!pending)return;await pending.catch(()=>{return})}startPolling(){const session=this.requireSession();if(this.interval)return;this.interval=setInterval(()=>{if(this.sessionEpoch!==session.epoch||this.options.automaticPollingAllowed?.()===false)return;this.poll({silent:this.options.automaticPollSilent}).catch((cause)=>{if(this.sessionEpoch===session.epoch)this.lastPollingFailure=asError(cause)})},MESSAGING_POLL_INTERVAL_MS)}pollingError(){return this.lastPollingFailure}stopPolling(){if(this.interval)clearInterval(this.interval);this.interval=null}async sendText(input){const session=this.requireSession();const contact=await this.lookupBapForSession(input.recipientBapId,session);this.assertCurrentSession(session);if(contact.kind!=="known")throw new MessagingCoordinatorError("recipient_unknown","No verified messaging identity exists for that BAP profile.");const message=makeMessage(session.identityKey,contact.binding.binding.walletIdentityKey,input.text,input.messageId??this.messageId(),this.now().toISOString());return await this.withActiveSend(session,message.messageId,async()=>{await session.history.persistOutgoingPending(session.partition,message);this.assertCurrentSession(session);return await this.sendDurably(session,message)})}async retry(messageId){const session=this.requireSession();return await this.withActiveSend(session,messageId,async()=>{const read=await session.history.read(session.partition);this.assertCurrentSession(session);const existing=read.snapshot.messages[messageId];if(!existing||existing.direction!=="outgoing"||existing.state!=="pending"&&existing.state!=="failed"&&existing.state!=="ambiguous")throw new MessagingCoordinatorError("retry_unavailable","Only a pending, failed, or delivery-ambiguous outgoing message can be retried.");this.assertCurrentSession(session);await session.history.retryOutgoing(session.partition,messageId);this.assertCurrentSession(session);return await this.sendDurably(session,existing.message)})}async withActiveSend(session,messageId,send){this.assertCurrentSession(session);if(this.activeSends.get(messageId)===session.epoch)throw new MessagingCoordinatorError("send_in_progress","This message is still sending.");this.activeSends.set(messageId,session.epoch);try{return await send()}finally{if(this.activeSends.get(messageId)===session.epoch)this.activeSends.delete(messageId)}}async deleteMessages(conversationId,messageIds){const session=this.requireSession();if(!session.durableHistory)throw new MessagingCoordinatorError("delete_unavailable","Owner-scoped message deletion is unavailable while message history is offline.");const read=await session.history.read(session.partition);this.assertCurrentSession(session);if(!read.snapshot.conversations[conversationId])throw new MessagingCoordinatorError("delete_stale","This conversation is no longer available. Refresh the inbox and retry.");const requested=[...new Set(messageIds)];if(requested.length===0)return{deletedMessageIds:[],failedMessageIds:[]};if(requested.some((id)=>read.snapshot.messages[id]?.message.conversationId!==conversationId))throw new MessagingCoordinatorError("delete_stale","One or more selected messages no longer belong to this conversation. Refresh the inbox and retry.");const result=await session.durableHistory.deleteMessages(session.identityKey,conversationId,requested);this.assertCurrentSession(session);await this.applyDeletedMessages(session,result.deletedMessageIds);return result}async deleteConversation(conversationId,messageIds){const session=this.requireSession();if(!session.durableHistory)throw new MessagingCoordinatorError("delete_unavailable","Owner-scoped conversation deletion is unavailable while message history is offline.");const read=await session.history.read(session.partition);this.assertCurrentSession(session);if(!read.snapshot.conversations[conversationId])throw new MessagingCoordinatorError("delete_stale","This conversation is no longer available. Refresh the inbox and retry.");const localMessageIds=Object.entries(read.snapshot.messages).filter(([,entry])=>entry.message.conversationId===conversationId).map(([id])=>id);const selectedIds=new Set(messageIds.filter((id)=>read.snapshot.messages[id]?.message.conversationId===conversationId));const result=await session.durableHistory.deleteConversation(session.identityKey,conversationId,[...new Set([...localMessageIds,...selectedIds])]);this.assertCurrentSession(session);await this.applyDeletedMessages(session,result.deletedMessageIds);return result}async applyDeletedMessages(session,messageIds){if(messageIds.length===0)return;this.assertCurrentSession(session);await session.history.deleteMessages(session.partition,messageIds);this.assertCurrentSession(session)}async lookupBap(bapId){return await this.lookupBapForSession(bapId,this.requireSession())}async lookupBapForSession(bapId,session){this.assertCurrentSession(session);const binding=await this.directoryResult(()=>this.options.directory.lookup(bapId),session);this.assertCurrentSession(session);if(!binding||binding.binding.bapId!==bapId)return{kind:"unknown",identityKey:""};await this.assertReverseBinding(binding,session);await this.recordContactOrKeyChange(binding,session);this.assertCurrentSession(session);return{kind:"known",binding}}async resolveSender(identityKey){assertCompressedIdentityKey(identityKey,"sender identity key");return await this.resolveSenderForSession(identityKey,this.requireSession())}async resolveSenderForSession(identityKey,session){assertCompressedIdentityKey(identityKey,"sender identity key");this.assertCurrentSession(session);const binding=await this.directoryResult(()=>this.options.directory.reverseLookup(identityKey),session);this.assertCurrentSession(session);if(!binding||binding.binding.walletIdentityKey.toLowerCase()!==identityKey.toLowerCase())return{kind:"unknown",identityKey};await this.assertReverseBinding(binding,session);await this.recordContactOrKeyChange(binding,session);this.assertCurrentSession(session);return{kind:"known",binding}}async acceptKeyChange(conversationId,newIdentityKey){const session=this.requireSession();const read=await session.history.read(session.partition);this.assertCurrentSession(session);const pending=read.snapshot.keyChanges.find((change)=>change.conversationId===conversationId&&change.newIdentityKey.toLowerCase()===newIdentityKey.toLowerCase()&&!change.acceptedAt);if(!pending)throw new MessagingCoordinatorError("key_change_missing","No unaccepted matching key change exists.");this.assertCurrentSession(session);await session.history.acceptKeyChange(session.partition,conversationId,newIdentityKey);this.assertCurrentSession(session);const snapshotRead=await session.history.read(session.partition);this.assertCurrentSession(session);const snapshot=snapshotRead.snapshot;const prior=snapshot.contacts[pending.oldIdentityKey.toLowerCase()];if(prior){this.assertCurrentSession(session);await session.history.upsertContact(session.partition,{...prior,identityKey:newIdentityKey,keySnapshot:newIdentityKey,observedAt:this.now().toISOString()});this.assertCurrentSession(session)}}async markRead(conversationId){const session=this.requireSession();this.assertCurrentSession(session);await session.history.markRead(session.partition,conversationId);this.assertCurrentSession(session)}async block(identityKey){const session=this.requireSession();assertCompressedIdentityKey(identityKey,"blocked identity key");const block={identityKey,blockedAt:this.now().toISOString(),serverSync:"pending"};this.assertCurrentSession(session);await session.history.block(session.partition,block);this.assertCurrentSession(session);try{this.assertCurrentSession(session);await session.transport.blockSender(identityKey);this.assertCurrentSession(session);await session.history.mutate(session.partition,(snapshot)=>updateBlock(snapshot,identityKey,"synced"));this.assertCurrentSession(session)}catch(cause){this.assertCurrentSession(session);await session.history.mutate(session.partition,(snapshot)=>updateBlock(snapshot,identityKey,"failed"));this.assertCurrentSession(session);throw cause}}async unblock(identityKey){const session=this.requireSession();assertCompressedIdentityKey(identityKey,"unblocked identity key");const read=await session.history.read(session.partition);this.assertCurrentSession(session);const snapshot=read.snapshot;if(!snapshot.blocks[identityKey.toLowerCase()])return;this.assertCurrentSession(session);await session.history.mutate(session.partition,(current)=>updateBlock(current,identityKey,"unblock-pending"));this.assertCurrentSession(session);await session.transport.unblockSender(identityKey);this.assertCurrentSession(session);await session.history.unblock(session.partition,identityKey);this.assertCurrentSession(session)}async disconnect(){const partition=this.partition;this.sessionEpoch+=1;this.stopPolling();this.inFlightPollAbort?.abort();this.inFlightPoll=null;this.inFlightPollEpoch=null;this.inFlightPollAbort=null;this.activeSends.clear();if(partition)this.options.history.clearSessionCache(partition.identityFingerprint);this.partition=null;this.identityKey=null;this.setupState="ineligible";this.lastPollingFailure=null}async pollOnce(session,signal,readOptions={}){this.assertCurrentSession(session);await this.options.beforePoll?.(readOptions);this.assertCurrentSession(session);if(session.durableHistory){const reconciliation=await session.durableHistory.reconcile(session.identityKey,signal);this.assertCurrentSession(session);await this.applyDeletedMessages(session,reconciliation.removedMessageIds??[]);this.assertCurrentSession(session);const total2={accepted:0,quarantined:0,transient:reconciliation.pendingFailures,duplicates:0,blocked:0,acknowledgementFailures:0};const resolvedContacts=new Set;for(const record of reconciliation.records){this.assertCurrentSession(session);const outcome=await this.processArchived(session,record,resolvedContacts);total2[outcome]+=1}this.assertCurrentSession(session);this.lastPollingFailure=reconciliation.pendingFailures>0?new MessagingCoordinatorError("history_sync_incomplete","Messages could not fully sync. Retry to continue."):null;return total2}const pages=await session.transport.listRaw();this.assertCurrentSession(session);const total={accepted:0,quarantined:0,transient:0,duplicates:0,blocked:0,acknowledgementFailures:0};for(const page of pages)for(const raw of page.messages){try{this.assertCurrentSession(session);const result=await this.processRaw(session,raw);this.assertCurrentSession(session);total[result]+=1}catch(cause){if(isSessionChanged(cause))throw cause;total.transient+=1}}return total}async processArchived(session,record,resolvedContacts){this.assertCurrentSession(session);if(record.direction==="outbound"&&this.activeSends.get(record.messageId)===session.epoch)return"duplicates";const read=await session.history.read(session.partition);this.assertCurrentSession(session);const snapshot=read.snapshot;const existing=snapshot.messages[record.messageId];if(record.direction==="outbound"&&this.activeSends.get(record.messageId)===session.epoch)return"duplicates";if(existing){if(record.direction==="outbound"){const outgoingState=archivedOutgoingState(record.deliveryState);if(existing.state!==outgoingState||!snapshot.dedupeIds.includes(record.messageId)){await session.history.reconcileRemoteMessage(session.partition,existing.message,outgoingState)}this.assertCurrentSession(session)}return"duplicates"}if(snapshot.quarantine[record.messageId])return"quarantined";if(record.direction==="inbound"){const block=snapshot.blocks[record.sender.toLowerCase()];if(block&&block.serverSync!=="unblock-pending")return"blocked"}const result=await session.transport.decryptArchived(record);this.assertCurrentSession(session);if(result.kind==="rejected"){if(result.disposition==="transient")return"transient";const quarantine={rawId:record.messageId,senderIdentityKey:record.sender,receivedAt:this.now().toISOString(),errorCode:permanentCode(result.error),rawCiphertextBase64:Utils4.toBase64(Array.from(new TextEncoder().encode(record.body)))};await session.history.persistQuarantine(session.partition,quarantine);this.assertCurrentSession(session);return"quarantined"}await session.history.reconcileRemoteMessage(session.partition,result.message,archivedOutgoingState(record.deliveryState));this.assertCurrentSession(session);const participant=record.direction==="inbound"?record.sender:record.recipient;if(!resolvedContacts.has(participant.toLowerCase())){resolvedContacts.add(participant.toLowerCase());await this.resolveContactBestEffort(participant,session)}return"accepted"}async processRaw(session,raw){this.assertCurrentSession(session);const read=await session.history.read(session.partition);this.assertCurrentSession(session);const snapshot=read.snapshot;if(snapshot.dedupeIds.includes(raw.messageId)){await this.resolveContactBestEffort(raw.sender,session);return await this.acknowledgeAfterDurability(raw.messageId,"duplicates",session)}const block=snapshot.blocks[raw.sender.toLowerCase()];if(block&&block.serverSync!=="unblock-pending")return await this.acknowledgeAfterDurability(raw.messageId,"blocked",session);this.assertCurrentSession(session);const result=await session.transport.decrypt(raw);this.assertCurrentSession(session);if(result.kind==="rejected"){if(result.disposition==="transient")return"transient";console.warn("[mapapp-messaging] quarantining invalid message",{messageId:raw.messageId,senderIdentityKey:raw.sender,code:permanentCode(result.error),reason:result.error.message.slice(0,256)});const record={rawId:raw.messageId,senderIdentityKey:raw.sender,receivedAt:this.now().toISOString(),errorCode:permanentCode(result.error),rawCiphertextBase64:Utils4.toBase64(Array.from(new TextEncoder().encode(raw.body)))};this.assertCurrentSession(session);await session.history.persistQuarantine(session.partition,record);this.assertCurrentSession(session);return await this.acknowledgeAfterDurability(raw.messageId,"quarantined",session)}this.assertCurrentSession(session);await session.history.persistIncoming(session.partition,result.message);this.assertCurrentSession(session);await this.resolveContactBestEffort(raw.sender,session);return await this.acknowledgeAfterDurability(raw.messageId,"accepted",session)}async resolveContactBestEffort(identityKey,session){try{await this.resolveSenderForSession(identityKey,session)}catch(cause){if(isSessionChanged(cause))throw cause}}async acknowledgeAfterDurability(messageId,state,session){this.assertCurrentSession(session);try{await session.transport.acknowledge([messageId]);this.assertCurrentSession(session);return state}catch(cause){this.assertCurrentSession(session);if(isSessionChanged(cause))throw cause;return"acknowledgementFailures"}}async sendDurably(session,message){this.assertCurrentSession(session);await this.options.beforeSend?.();this.assertCurrentSession(session);if(session.durableHistory){let result;try{result=await session.durableHistory.send(session.identityKey,message)}catch(cause){this.assertCurrentSession(session);const saved2=await session.history.reconcileRemoteMessage(session.partition,message,"ambiguous");this.assertCurrentSession(session);if(saved2.snapshot.messages[message.messageId]?.state==="sent")return{messageId:message.messageId};throw new MessagingCoordinatorError("delivery_unconfirmed","Delivery could not be confirmed. MapApp will not resend it automatically.",{cause})}this.assertCurrentSession(session);const next=result.state==="accepted"?"sent":result.state==="failed"?"failed":"ambiguous";const saved=await session.history.reconcileRemoteMessage(session.partition,message,next);this.assertCurrentSession(session);if(result.state!=="accepted"){if(saved.snapshot.messages[message.messageId]?.state==="sent")return{messageId:message.messageId};const code=result.state==="unknown"?"delivery_unconfirmed":"send_failed";const detail=result.state==="unknown"?"Delivery could not be confirmed. MapApp will not resend it automatically.":"Message could not be sent.";throw new MessagingCoordinatorError(code,detail)}if(result.attempted)this.signalSent(session,message);return{messageId:message.messageId}}try{this.assertCurrentSession(session);const sent=await session.transport.send(message);this.assertCurrentSession(session);if(sent.messageId!==message.messageId)throw new MessagingCoordinatorError("transport_message_id_mismatch","MessageBox returned a different message ID.")}catch(cause){this.assertCurrentSession(session);await session.history.transitionOutgoing(session.partition,message.messageId,"ambiguous","delivery_unconfirmed");this.assertCurrentSession(session);throw cause}this.assertCurrentSession(session);await session.history.transitionOutgoing(session.partition,message.messageId,"sent");this.assertCurrentSession(session);this.signalSent(session,message);return{messageId:message.messageId}}signalSent(session,message){this.assertCurrentSession(session);Promise.resolve().then(()=>{this.assertCurrentSession(session);return this.options.onSent?.(message.recipientIdentityKey,session.identityKey)}).catch(()=>{return})}async assertReverseBinding(binding,session){this.assertCurrentSession(session);const reverse=await this.directoryResult(()=>this.options.directory.reverseLookup(binding.binding.walletIdentityKey),session);this.assertCurrentSession(session);if(!reverse||reverse.binding.bapId!==binding.binding.bapId||reverse.binding.walletIdentityKey.toLowerCase()!==binding.binding.walletIdentityKey.toLowerCase()||reverse.keyVersion!==binding.keyVersion)throw new MessagingCoordinatorError("directory_reverse_mismatch","Messaging directory forward and reverse bindings disagree.")}async recordContactOrKeyChange(binding,session){this.assertCurrentSession(session);const read=await session.history.read(session.partition);this.assertCurrentSession(session);const snapshot=read.snapshot;if(snapshot.contacts[binding.binding.walletIdentityKey.toLowerCase()]?.bapId===binding.binding.bapId)return;const existing=Object.values(snapshot.contacts).filter((contact2)=>contact2.bapId===binding.binding.bapId).sort((left,right)=>right.observedAt.localeCompare(left.observedAt))[0];if(existing&&existing.identityKey.toLowerCase()!==binding.binding.walletIdentityKey.toLowerCase()){const change=snapshot.keyChanges.find((item)=>item.oldIdentityKey.toLowerCase()===existing.identityKey.toLowerCase()&&item.newIdentityKey.toLowerCase()===binding.binding.walletIdentityKey.toLowerCase());if(change?.acceptedAt){this.assertCurrentSession(session);await session.history.upsertContact(session.partition,{identityKey:binding.binding.walletIdentityKey,bapId:binding.binding.bapId,keySnapshot:binding.binding.walletIdentityKey,observedAt:binding.updatedAt});this.assertCurrentSession(session);return}if(change)throw new MessagingCoordinatorError("key_change_pending","This contact changed its messaging key; accept the change before sending.");this.assertCurrentSession(session);await session.history.recordKeyChange(session.partition,{conversationId:conversationIdForIdentityKeys(session.identityKey,existing.identityKey),oldIdentityKey:existing.identityKey,newIdentityKey:binding.binding.walletIdentityKey,observedAt:this.now().toISOString()});this.assertCurrentSession(session);throw new MessagingCoordinatorError("key_change_pending","This contact changed its messaging key; accept the change before sending.")}const contact={identityKey:binding.binding.walletIdentityKey,bapId:binding.binding.bapId,keySnapshot:binding.binding.walletIdentityKey,observedAt:binding.updatedAt};this.assertCurrentSession(session);await session.history.upsertContact(session.partition,contact);this.assertCurrentSession(session)}assertReceipt(){if(this.options.privateMessagingReady?.()!==true)throw new MessagingCoordinatorError("private_messaging_unavailable","Private messaging requires the reviewed three-wallet release profile.")}async readSetupState(identityKey,session){if(this.options.privateMessagingReady?.()!==true)return"ineligible";if(session)this.assertCurrentSession(session);const binding=await this.directoryResult(()=>this.options.directory.reverseLookup(identityKey),session);if(session)this.assertCurrentSession(session);if(binding?.binding.walletIdentityKey.toLowerCase()===identityKey.toLowerCase())return"registered";if(this.options.bapId){if(session)this.assertCurrentSession(session);const existing=await this.directoryResult(()=>this.options.directory.lookup(this.options.bapId),session);if(session)this.assertCurrentSession(session);if(existing&&existing.binding.bapId===this.options.bapId&&existing.binding.walletIdentityKey.toLowerCase()!==identityKey.toLowerCase())return"rotation-required"}return"eligible-unregistered"}requireSession(readOptions={}){this.assertReceipt();if(!this.identityKey||!this.partition)throw new MessagingCoordinatorError("messaging_not_started","Explicit wallet access is required before messaging.");const ports=readOptions.silent?this.options.silentReadPorts:this.options;if(!ports)throw new MessagingCoordinatorError("silent_access_unavailable","Silent messaging access is unavailable.");return{...ports,identityKey:this.identityKey,partition:this.partition,epoch:this.sessionEpoch}}async directoryResult(read,session){try{const value=await read();if(session)this.assertCurrentSession(session);return parseDirectoryResponse(value)}catch(cause){if(session)this.assertCurrentSession(session);if(cause instanceof MessagingDirectoryClientError&&cause.status===404)return null;throw cause}}assertEpoch(epoch){if(this.sessionEpoch!==epoch)throw new MessagingCoordinatorError("session_changed","Messaging wallet session changed while opening messaging.")}assertCurrentSession(session){if(this.sessionEpoch===session.epoch&&this.identityKey?.toLowerCase()===session.identityKey.toLowerCase()&&this.partition?.identityFingerprint===session.partition.identityFingerprint)return;session.history.clearSessionCache(session.partition.identityFingerprint);throw new MessagingCoordinatorError("session_changed","Messaging wallet session changed while messaging was in flight.")}isCurrentSession(session){try{this.assertCurrentSession(session);return true}catch(cause){if(!isSessionChanged(cause))throw cause;return false}}}function archivedOutgoingState(value){if(value==="accepted")return"sent";if(value==="failed")return"failed";return"ambiguous"}function makeMessage(senderIdentityKey,recipientIdentityKey,text,messageId,sentAt){return{version:MAPAPP_MESSAGE_VERSION,messageId,senderIdentityKey,recipientIdentityKey,conversationId:conversationIdForIdentityKeys(senderIdentityKey,recipientIdentityKey),sentAt,text}}function parseDirectoryResponse(value){if(!value||typeof value!=="object"||Array.isArray(value))return null;const outer=value;const candidate=isDirectoryBinding(outer)?outer:outer.binding;if(!candidate||typeof candidate!=="object"||Array.isArray(candidate))return null;const record=candidate;if(Object.keys(record).length!==3||!Object.hasOwn(record,"binding")||!Object.hasOwn(record,"keyVersion")||!Object.hasOwn(record,"updatedAt")||!Number.isSafeInteger(record.keyVersion)||record.keyVersion<1||typeof record.updatedAt!=="string"||!Number.isFinite(Date.parse(record.updatedAt)))return null;try{return{binding:assertMessagingIdentityV1(record.binding),keyVersion:record.keyVersion,updatedAt:record.updatedAt}}catch{return null}}function isDirectoryBinding(value){return Object.hasOwn(value,"binding")&&Object.hasOwn(value,"keyVersion")&&Object.hasOwn(value,"updatedAt")}function registrationResultMatches(value,identityKey){const binding=parseDirectoryResponse(value);return binding?.binding.walletIdentityKey.toLowerCase()===identityKey.toLowerCase()}function updateBlock(snapshot,identityKey,serverSync){const key=identityKey.toLowerCase();const block=snapshot.blocks[key];if(!block)return snapshot;return{...snapshot,blocks:{...snapshot.blocks,[key]:{...block,serverSync}}}}function permanentCode(error2){const code="code"in error2&&typeof error2.code==="string"?error2.code:"invalid_message";return code.slice(0,128)}function defaultMessageId(){const id=globalThis.crypto?.randomUUID?.();if(!id)throw new MessagingCoordinatorError("secure_random_unavailable","Secure random UUID support is required for messaging.");return id}function assertOriginator2(value){if(!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/.test(value))throw new TypeError("Messaging originator must be a canonical FQDN.")}function asError(value){return value instanceof Error?value:new Error("Messaging poll failed.")}function isSessionChanged(value){return value instanceof MessagingCoordinatorError&&value.code==="session_changed"}function createAgentMessagingExecutor(options){const inspector=createAgentWalletConnectionInspector({wallet:options.wallet,binding:options.binding});let session,busy=false;async function current(){return session??=await options.open()}async function run(caller,work){if(busy)return{kind:"blocked",reason:"execution_in_progress"};busy=true;try{const inspected=await inspector.inspect(caller);if(inspected.state!=="ready"){session?.close();session=undefined;return{kind:"blocked",reason:inspected.reason}}const value=await current();return await work(value)}catch(cause){session?.close();session=undefined;const code=cause&&typeof cause==="object"&&"code"in cause?cause.code:undefined;return{kind:"blocked",reason:typeof code==="string"&&/^[a-z0-9_]{1,100}$/u.test(code)?code:"messaging_operation_unavailable"}}finally{session?.clearRecipient();busy=false}}return{enable(caller){return run(caller,async(value)=>({kind:"enabled",setup:await value.coordinator.registerMessaging(),disclosure:MESSAGING_PRIVACY_DISCLOSURE,delegation:"user_and_agent",profile:options.binding.route==="existing_brc100"?"brc100_http_participant":"direct_wallet_node_development"}))},send(caller,input){return run(caller,async(value)=>{if(!input||typeof input!=="object"||Object.keys(input).some((key)=>!["operationId","recipientBapId","expectedRecipientIdentityKey","text"].includes(key))||!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/u.test(input.operationId)||!/^[1-9A-HJ-NP-Za-km-z]{20,64}$/u.test(input.recipientBapId)||!/^(02|03)[0-9a-f]{64}$/u.test(input.expectedRecipientIdentityKey)||typeof input.text!=="string"||!input.text.trim()||new TextEncoder().encode(input.text).length>DEFAULT_MESSAGE_PLAINTEXT_MAX_BYTES||new TextDecoder().decode(new TextEncoder().encode(input.text))!==input.text)return{kind:"blocked",reason:"invalid_message_input"};const envelope=serializeMapAppMessageV1({version:"mapapp-message-v1",messageId:input.operationId,senderIdentityKey:options.binding.expectedIdentityKey,recipientIdentityKey:input.expectedRecipientIdentityKey,conversationId:conversationIdForIdentityKeys(options.binding.expectedIdentityKey,input.expectedRecipientIdentityKey),sentAt:new Date().toISOString(),text:input.text});if(new TextEncoder().encode(envelope).length>DEFAULT_MESSAGE_PLAINTEXT_MAX_BYTES)return{kind:"blocked",reason:"message_too_large"};const state=await value.coordinator.state();if(state.setup!=="registered")return{kind:"blocked",reason:"messaging_registration_required"};const existing=state.snapshot.messages[input.operationId];if(existing){if(existing.direction!=="outgoing"||existing.message.text!==input.text||existing.message.recipientIdentityKey!==input.expectedRecipientIdentityKey)return{kind:"blocked",reason:"operation_input_conflict"};return{kind:"status",messageId:input.operationId,state:existing.state,retryAllowed:false}}value.pinRecipient(input.recipientBapId,input.expectedRecipientIdentityKey);await value.coordinator.sendText({recipientBapId:input.recipientBapId,text:input.text,messageId:input.operationId});let notification={state:"not_requested"};if(value.notifySent){try{notification=await value.notifySent(input.expectedRecipientIdentityKey)}catch{notification={state:"unavailable"}}}return{kind:"sent",messageId:input.operationId,state:"sent",retryAllowed:false,transportAccepted:true,humanRead:"unknown",notification}})},status(caller,operationId){return run(caller,async(value)=>{const entry=(await value.coordinator.state()).snapshot.messages[operationId];return entry?.direction==="outgoing"?{kind:"status",messageId:operationId,state:entry.state,retryAllowed:false}:{kind:"blocked",reason:"operation_not_found"}})},sync(caller){return run(caller,async(value)=>({kind:"synced",result:await value.coordinator.poll()}))},inbox(caller,limit=20){return run(caller,async(value)=>{if(!Number.isInteger(limit)||limit<1||limit>50)return{kind:"blocked",reason:"invalid_inbox_limit"};const entries=Object.values((await value.coordinator.state()).snapshot.messages).sort((a,b)=>b.message.sentAt.localeCompare(a.message.sentAt));const messages=[];for(const entry of entries.slice(0,limit)){if(new TextEncoder().encode(JSON.stringify([...messages,entry])).length>60000)break;messages.push(entry)}return{kind:"inbox",messages,truncated:entries.length>messages.length,content:"untrusted_private_messages"}})},close(){session?.close();session=undefined}}}import{Utils as Utils6}from"@bsv/sdk";import{Hash as Hash2,Utils as Utils5}from"@bsv/sdk";var MESSAGING_HISTORY_PROTOCOL=[1,"mapapp messaging history"];var MESSAGING_HISTORY_KEY_ID="snapshot-v1";var MESSAGING_HISTORY_MAX_SNAPSHOT_BYTES=1024*1024;var MESSAGING_HISTORY_MAX_RECORDS=5000;var MESSAGING_HISTORY_MAX_QUARANTINE_BYTES=16*1024;function maxActivityTimestamp(left,right){return Date.parse(left)>=Date.parse(right)?left:right}class MessagingHistoryError extends Error{code;constructor(code,message,options){super(message,options);this.code=code;this.name="MessagingHistoryError"}}function identityFingerprint(identityKey){assertCompressedIdentityKey(identityKey,"wallet identity key");return Utils5.toHex(Hash2.sha256(Array.from(new TextEncoder().encode(identityKey.toLowerCase()))))}function createEmptySnapshot(identityKey,now){const fingerprint=identityFingerprint(identityKey);assertTimestamp(now,"now");return{version:1,identityKey,identityFingerprint:fingerprint,createdAt:now,updatedAt:now,conversations:{},messages:{},dedupeIds:[],contacts:{},blocks:{},quarantine:{},keyChanges:[],metadata:{schemaVersion:1}}}function parseMessagingHistorySnapshot(value,expectedIdentityKey){const r=record(value,["version","identityKey","identityFingerprint","createdAt","updatedAt","conversations","messages","dedupeIds","contacts","blocks","quarantine","keyChanges","metadata"],"snapshot");if(r.version!==1||typeof r.identityKey!=="string"||typeof r.identityFingerprint!=="string")fail("snapshot_corrupt","Unsupported messaging history snapshot.");assertCompressedIdentityKey(r.identityKey,"snapshot identity key");if(r.identityFingerprint!==identityFingerprint(r.identityKey)||expectedIdentityKey&&r.identityKey.toLowerCase()!==expectedIdentityKey.toLowerCase())fail("identity_mismatch","Messaging history belongs to another wallet identity.");assertTimestamp(r.createdAt,"createdAt");assertTimestamp(r.updatedAt,"updatedAt");const conversations=parseMap(r.conversations,MESSAGING_HISTORY_MAX_RECORDS,parseConversation);const messages=parseMap(r.messages,MESSAGING_HISTORY_MAX_RECORDS,parseHistoryMessage);const dedupeIds=parseStrings(r.dedupeIds,MESSAGING_HISTORY_MAX_RECORDS,"dedupeIds");if(new Set(dedupeIds).size!==dedupeIds.length)fail("snapshot_corrupt","Duplicate message dedupe IDs.");const contacts=parseMap(r.contacts,MESSAGING_HISTORY_MAX_RECORDS,parseContact);const blocks=parseMap(r.blocks,MESSAGING_HISTORY_MAX_RECORDS,parseBlock);const quarantine=parseMap(r.quarantine,MESSAGING_HISTORY_MAX_RECORDS,parseQuarantine);if(!Array.isArray(r.keyChanges)||r.keyChanges.length>MESSAGING_HISTORY_MAX_RECORDS)fail("snapshot_corrupt","Invalid key-change history.");const keyChanges=r.keyChanges.map(parseKeyChange);const metadata=record(r.metadata,["schemaVersion","lastExportedAt"],"metadata",["lastExportedAt"]);if(metadata.schemaVersion!==1||metadata.lastExportedAt!==undefined&&typeof metadata.lastExportedAt!=="string")fail("snapshot_corrupt","Invalid messaging history metadata.");if(metadata.lastExportedAt!==undefined)assertTimestamp(metadata.lastExportedAt,"lastExportedAt");for(const[id,message]of Object.entries(messages)){if(id!==message.message.messageId)fail("snapshot_corrupt","Message map key mismatch.");if(!conversations[message.message.conversationId])fail("snapshot_corrupt","Message has no conversation.")}const repairedConversations=Object.fromEntries(Object.entries(conversations).map(([conversationId,conversation])=>{const latest=Object.values(messages).filter((entry)=>entry.message.conversationId===conversationId).reduce((current,entry)=>current===null?entry.message.sentAt:maxActivityTimestamp(current,entry.message.sentAt),null);return[conversationId,latest?{...conversation,lastMessageAt:latest}:conversation]}));return{version:1,identityKey:r.identityKey,identityFingerprint:r.identityFingerprint,createdAt:r.createdAt,updatedAt:r.updatedAt,conversations:repairedConversations,messages,dedupeIds,contacts,blocks,quarantine,keyChanges,metadata:metadata.lastExportedAt?{schemaVersion:1,lastExportedAt:metadata.lastExportedAt}:{schemaVersion:1}}}function parseMessagingHistoryEnvelope(value){const r=record(value,["version","identityFingerprint","revision","ciphertextBase64","schemaVersion","updatedAt"],"envelope");if(r.version!==1||r.schemaVersion!==1||typeof r.identityFingerprint!=="string"||!/^[0-9a-f]{64}$/.test(r.identityFingerprint)||!safePositive(r.revision)||typeof r.ciphertextBase64!=="string"||!canonicalBase64(r.ciphertextBase64)||typeof r.updatedAt!=="string")fail("envelope_corrupt","Invalid encrypted messaging history envelope.");if(base64Bytes(r.ciphertextBase64)>MESSAGING_HISTORY_MAX_SNAPSHOT_BYTES)fail("snapshot_too_large","Encrypted messaging history exceeds its limit.");assertTimestamp(r.updatedAt,"envelope updatedAt");return{version:1,identityFingerprint:r.identityFingerprint,revision:r.revision,ciphertextBase64:r.ciphertextBase64,schemaVersion:1,updatedAt:r.updatedAt}}function parseConversation(value){const r=record(value,["conversationId","participantIdentityKey","unreadCount","lastMessageAt"],"conversation");if(typeof r.conversationId!=="string"||typeof r.participantIdentityKey!=="string"||!Number.isSafeInteger(r.unreadCount)||r.unreadCount<0||typeof r.lastMessageAt!=="string")fail("snapshot_corrupt","Invalid conversation.");assertCompressedIdentityKey(r.participantIdentityKey,"conversation participant");assertTimestamp(r.lastMessageAt,"conversation lastMessageAt");return{conversationId:r.conversationId,participantIdentityKey:r.participantIdentityKey,unreadCount:r.unreadCount,lastMessageAt:r.lastMessageAt}}function parseHistoryMessage(value){const r=record(value,["message","direction","state","receivedAt","failureCode"],"history message",["receivedAt","failureCode"]);const message=assertMapAppMessageV1(r.message);if(r.direction!=="incoming"&&r.direction!=="outgoing")fail("snapshot_corrupt","Invalid message direction.");if(r.state!=="pending"&&r.state!=="queued"&&r.state!=="failed"&&r.state!=="ambiguous"&&r.state!=="sent"&&r.state!=="received")fail("snapshot_corrupt","Invalid message state.");if(r.direction==="incoming"&&r.state!=="received")fail("snapshot_corrupt","Incoming messages must be received.");if((r.state==="failed"||r.state==="ambiguous")&&(typeof r.failureCode!=="string"||r.failureCode.length===0||r.failureCode.length>128))fail("snapshot_corrupt","Failed or ambiguous messages require a bounded failure code.");if(r.receivedAt!==undefined)assertTimestamp(r.receivedAt,"receivedAt");if(r.failureCode!==undefined&&(typeof r.failureCode!=="string"||r.failureCode.length>128))fail("snapshot_corrupt","Invalid message failure code.");return{message,direction:r.direction,state:r.state,...r.receivedAt?{receivedAt:r.receivedAt}:{},...r.failureCode?{failureCode:r.failureCode}:{}}}function parseContact(value){const r=record(value,["identityKey","bapId","profile","keySnapshot","observedAt"],"contact",["bapId","profile"]);if(typeof r.identityKey!=="string"||typeof r.keySnapshot!=="string"||typeof r.observedAt!=="string")fail("snapshot_corrupt","Invalid contact.");assertCompressedIdentityKey(r.identityKey,"contact identity");assertCompressedIdentityKey(r.keySnapshot,"contact key snapshot");if(r.bapId!==undefined&&(typeof r.bapId!=="string"||r.bapId.length>256))fail("snapshot_corrupt","Invalid contact BAP ID.");const profile=r.profile===undefined?undefined:parseStringMap(r.profile,32);assertTimestamp(r.observedAt,"contact observedAt");return{identityKey:r.identityKey,keySnapshot:r.keySnapshot,observedAt:r.observedAt,...r.bapId?{bapId:r.bapId}:{},...profile?{profile}:{}}}function parseBlock(value){const r=record(value,["identityKey","blockedAt","serverSync"],"block");if(typeof r.identityKey!=="string"||typeof r.blockedAt!=="string"||r.serverSync!=="pending"&&r.serverSync!=="synced"&&r.serverSync!=="failed"&&r.serverSync!=="unblock-pending")fail("snapshot_corrupt","Invalid block.");assertCompressedIdentityKey(r.identityKey,"block identity");assertTimestamp(r.blockedAt,"blockedAt");return{identityKey:r.identityKey,blockedAt:r.blockedAt,serverSync:r.serverSync}}function parseQuarantine(value){const r=record(value,["rawId","senderIdentityKey","receivedAt","errorCode","rawCiphertextBase64"],"quarantine");if(typeof r.rawId!=="string"||typeof r.senderIdentityKey!=="string"||typeof r.receivedAt!=="string"||typeof r.errorCode!=="string"||r.errorCode.length>128||typeof r.rawCiphertextBase64!=="string"||!canonicalBase64(r.rawCiphertextBase64)||base64Bytes(r.rawCiphertextBase64)>MESSAGING_HISTORY_MAX_QUARANTINE_BYTES)fail("snapshot_corrupt","Invalid quarantine record.");assertCompressedIdentityKey(r.senderIdentityKey,"quarantine sender");assertTimestamp(r.receivedAt,"quarantine receivedAt");return{rawId:r.rawId,senderIdentityKey:r.senderIdentityKey,receivedAt:r.receivedAt,errorCode:r.errorCode,rawCiphertextBase64:r.rawCiphertextBase64}}function parseKeyChange(value){const r=record(value,["conversationId","oldIdentityKey","newIdentityKey","observedAt","acceptedAt"],"key change",["acceptedAt"]);if(typeof r.conversationId!=="string"||typeof r.oldIdentityKey!=="string"||typeof r.newIdentityKey!=="string"||typeof r.observedAt!=="string")fail("snapshot_corrupt","Invalid key change.");assertCompressedIdentityKey(r.oldIdentityKey,"old identity key");assertCompressedIdentityKey(r.newIdentityKey,"new identity key");if(r.oldIdentityKey.toLowerCase()===r.newIdentityKey.toLowerCase())fail("snapshot_corrupt","Key change cannot retain the same key.");assertTimestamp(r.observedAt,"key change observedAt");if(r.acceptedAt!==undefined)assertTimestamp(r.acceptedAt,"key change acceptedAt");return{conversationId:r.conversationId,oldIdentityKey:r.oldIdentityKey,newIdentityKey:r.newIdentityKey,observedAt:r.observedAt,...r.acceptedAt?{acceptedAt:r.acceptedAt}:{}}}function parseMap(value,max,parser){if(!isPlainRecord(value)||Object.keys(value).length>max)fail("snapshot_corrupt","Invalid messaging history record map.");const result={};for(const[key,item]of Object.entries(value)){if(!key||key.length>256)fail("snapshot_corrupt","Invalid messaging history key.");result[key]=parser(item)}return result}function parseStringMap(value,max){if(!isPlainRecord(value)||Object.keys(value).length>max)fail("snapshot_corrupt","Invalid profile snapshot.");const result={};for(const[key,item]of Object.entries(value)){if(!key||key.length>64||typeof item!=="string"||item.length>1024)fail("snapshot_corrupt","Invalid profile snapshot field.");result[key]=item}return result}function parseStrings(value,max,name){if(!Array.isArray(value)||value.length>max||value.some((item)=>typeof item!=="string"||!item||item.length>256))fail("snapshot_corrupt",`Invalid ${name}.`);return[...value]}function record(value,keys,name,optional=[]){if(!isPlainRecord(value))fail("snapshot_corrupt",`${name} must be an object.`);const actual=Object.keys(value);if(actual.length<keys.length-optional.length||actual.length>keys.length||keys.some((key)=>!(key in value)&&!optional.includes(key)))fail("snapshot_corrupt",`${name} has unknown or missing fields.`);return value}function isPlainRecord(value){if(!value||typeof value!=="object"||Array.isArray(value))return false;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function safePositive(value){return typeof value==="number"&&Number.isSafeInteger(value)&&value>0}function assertTimestamp(value,name){if(typeof value!=="string"||!Number.isFinite(Date.parse(value))||value.length>64)fail("snapshot_corrupt",`Invalid ${name}.`)}function canonicalBase64(value){return/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)}function base64Bytes(value){return value.length/4*3-(value.endsWith("==")?2:value.endsWith("=")?1:0)}function fail(code,message){throw new MessagingHistoryError(code,message)}var encoder=new TextEncoder;var decoder=new TextDecoder("utf-8",{fatal:true});function createMessagingHistoryCrypto(options){const originator=options.originator===undefined?undefined:canonicalOriginator(options.originator);const maximum=options.maxSnapshotBytes??MESSAGING_HISTORY_MAX_SNAPSHOT_BYTES;if(!Number.isSafeInteger(maximum)||maximum<=0||maximum>MESSAGING_HISTORY_MAX_SNAPSHOT_BYTES)throw new MessagingHistoryError("invalid_bound","Invalid history snapshot byte limit.");return{async encrypt(snapshot){const plaintext=encoder.encode(JSON.stringify(snapshot));if(plaintext.byteLength>maximum)throw new MessagingHistoryError("snapshot_too_large","Messaging history snapshot exceeds its byte limit.");let encrypted;try{encrypted=await options.wallet.encrypt({plaintext:[...plaintext],protocolID:[...MESSAGING_HISTORY_PROTOCOL],keyID:MESSAGING_HISTORY_KEY_ID,counterparty:"self"},originator)}catch(cause){throw wrapped("wallet_encryption_failed","The connected wallet could not encrypt messaging history.",cause)}if(!Array.isArray(encrypted.ciphertext)||encrypted.ciphertext.length===0||encrypted.ciphertext.length>maximum||encrypted.ciphertext.some((byte)=>!Number.isInteger(byte)||byte<0||byte>255))throw new MessagingHistoryError("wallet_encryption_failed","Wallet returned invalid encrypted messaging history.");return bytesToBase64(encrypted.ciphertext)},async decrypt(ciphertextBase64,expectedIdentityKey){let ciphertext;try{ciphertext=base64ToBytes(ciphertextBase64,maximum)}catch(cause){throw wrapped("ciphertext_corrupt","Encrypted messaging history is invalid.",cause)}let decrypted;try{decrypted=await options.wallet.decrypt({ciphertext,protocolID:[...MESSAGING_HISTORY_PROTOCOL],keyID:MESSAGING_HISTORY_KEY_ID,counterparty:"self"},originator)}catch(cause){throw wrapped("wallet_decryption_failed","The connected wallet could not decrypt messaging history.",cause)}if(!Array.isArray(decrypted.plaintext)||decrypted.plaintext.length>maximum||decrypted.plaintext.some((byte)=>!Number.isInteger(byte)||byte<0||byte>255))throw new MessagingHistoryError("snapshot_corrupt","Wallet returned invalid messaging history plaintext.");try{return parseMessagingHistorySnapshot(JSON.parse(decoder.decode(new Uint8Array(decrypted.plaintext))),expectedIdentityKey)}catch(cause){throw wrapped("snapshot_corrupt","Decrypted messaging history is corrupt.",cause)}}}}function canonicalOriginator(value){const originator=value.trim().toLowerCase();if(originator.length===0||originator.length>250||originator.includes(":"))throw new MessagingHistoryError("invalid_originator","Wallet originator must be a canonical FQDN.");const labels=originator.split(".");if(labels.length<2||labels.some((label)=>!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)))throw new MessagingHistoryError("invalid_originator","Wallet originator must be a canonical FQDN.");return originator}function bytesToBase64(bytes){if(bytes.length===0||bytes.some((byte)=>!Number.isInteger(byte)||byte<0||byte>255))throw new TypeError("Invalid bytes.");return Utils6.toBase64([...bytes])}function base64ToBytes(value,maxBytes){if(typeof value!=="string"||!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value))throw new TypeError("Not canonical base64.");let result;try{result=Utils6.toArray(value,"base64")}catch{throw new TypeError("Not canonical base64.")}if(result.length===0||result.length>maxBytes||Utils6.toBase64(result)!==value)throw new RangeError("Ciphertext exceeds its limit or is not canonical.");return result}function wrapped(code,message,cause){return cause instanceof MessagingHistoryError?cause:new MessagingHistoryError(code,`${message}${cause instanceof Error&&cause.message?` ${cause.message}`:""}`,{cause})}class MessagingSessionCache{generation=0;identityGenerations=new Map;entries=new Map;get(identityFingerprint2){const entry=this.entries.get(identityFingerprint2);return entry?.generation===this.generation?{snapshot:structuredClone(entry.snapshot),revision:entry.revision}:undefined}capture(identityFingerprint2){return{generation:this.generation,identityGeneration:this.identityGenerations.get(identityFingerprint2)??0}}set(identityFingerprint2,snapshot,revision,token){const current=this.capture(identityFingerprint2);if(token&&(token.generation!==current.generation||token.identityGeneration!==current.identityGeneration))return;this.entries.set(identityFingerprint2,{generation:this.generation,snapshot:structuredClone(snapshot),revision})}clear(identityFingerprint2){if(identityFingerprint2){this.entries.delete(identityFingerprint2);this.identityGenerations.set(identityFingerprint2,(this.identityGenerations.get(identityFingerprint2)??0)+1)}else{this.entries.clear();this.generation++}}destroy(){this.clear()}}var STORE="snapshots";var DB_VERSION=1;var RETRIES=3;function createMessagingHistoryStore(options){const storage=options.storage??createIndexedDbMessagingHistoryStorage(resolveIndexedDb2(options.indexedDB));const crypto2=options.crypto??createMessagingHistoryCrypto({wallet:options.wallet,originator:options.originator});const now=options.now??(()=>new Date);const cache=options.cache??new MessagingSessionCache;const timestamp=()=>now().toISOString();const partitionFor=(identityKey)=>{assertCompressedIdentityKey(identityKey,"wallet identity key");return{identityKey,identityFingerprint:identityFingerprint(identityKey)}};async function load(partition){const envelope=await storage.get(partition.identityFingerprint);if(!envelope)return null;const valid=parseMessagingHistoryEnvelope(envelope);if(valid.identityFingerprint!==partition.identityFingerprint)throw new MessagingHistoryError("identity_mismatch","Encrypted messaging history partition does not match the connected identity.");const snapshot=await crypto2.decrypt(valid.ciphertextBase64,partition.identityKey);return{revision:valid.revision,snapshot}}async function commit(partition,mutation,expectedRevision,cacheToken=cache.capture(partition.identityFingerprint)){for(let attempt=0;attempt<RETRIES;attempt++){const loaded=await load(partition);const base=loaded??{revision:0,snapshot:createEmptySnapshot(partition.identityKey,timestamp())};if(expectedRevision!==undefined&&base.revision!==expectedRevision)throw new MessagingHistoryError("revision_conflict","Messaging history revision does not match the caller expectation.");const draft=structuredClone(base.snapshot);const changed=mutation(draft);if(changed===draft){cache.set(partition.identityFingerprint,base.snapshot,base.revision,cacheToken);return{revision:base.revision,snapshot:structuredClone(base.snapshot)}}const snapshot=parseMessagingHistorySnapshot({...changed,updatedAt:timestamp()},partition.identityKey);const ciphertextBase64=await crypto2.encrypt(snapshot);const next={version:1,identityFingerprint:partition.identityFingerprint,revision:base.revision+1,ciphertextBase64,schemaVersion:1,updatedAt:snapshot.updatedAt};if(await storage.compareAndSwap(partition.identityFingerprint,loaded?.revision??null,next)){cache.set(partition.identityFingerprint,snapshot,next.revision,cacheToken);return{revision:next.revision,snapshot:structuredClone(snapshot)}}if(expectedRevision!==undefined)throw new MessagingHistoryError("revision_conflict","Messaging history changed before it could be committed.")}throw new MessagingHistoryError("revision_conflict","Messaging history changed repeatedly; retry the operation.")}const ensureConversation=(snapshot,message)=>{const other=message.senderIdentityKey.toLowerCase()===snapshot.identityKey.toLowerCase()?message.recipientIdentityKey:message.senderIdentityKey;if(other.toLowerCase()===snapshot.identityKey.toLowerCase())throw new MessagingHistoryError("self_message","Self-messaging records are forbidden.");const existing=snapshot.conversations[message.conversationId];if(!existing)assertCapacity(snapshot.conversations,"conversations");const conversation=existing??{conversationId:message.conversationId,participantIdentityKey:other,unreadCount:0,lastMessageAt:message.sentAt};return{...snapshot,conversations:{...snapshot.conversations,[message.conversationId]:{...conversation,lastMessageAt:maxActivityTimestamp(conversation.lastMessageAt,message.sentAt)}}}};return{async open(identityKey){const partition=partitionFor(identityKey);const cacheToken=cache.capture(partition.identityFingerprint);if(!await load(partition))await commit(partition,(snapshot)=>({...snapshot}),undefined,cacheToken);return partition},async read(partition){assertPartition(partition);const cached=cache.get(partition.identityFingerprint);if(cached)return cached;const cacheToken=cache.capture(partition.identityFingerprint);const loaded=await load(partition);if(!loaded){await commit(partition,(snapshot)=>({...snapshot}),undefined,cacheToken);return await load(partition)}cache.set(partition.identityFingerprint,loaded.snapshot,loaded.revision,cacheToken);return{revision:loaded.revision,snapshot:structuredClone(loaded.snapshot)}},async exportEnvelope(partition){assertPartition(partition);const envelope=await storage.get(partition.identityFingerprint);if(!envelope)throw new MessagingHistoryError("history_missing","No encrypted messaging history exists to export.");return parseMessagingHistoryEnvelope(envelope)},mutate:commit,persistOutgoingPending(partition,message,expectedRevision){return commit(partition,(snapshot)=>{const valid=assertOutgoingForPartition(message,snapshot.identityKey);const next=ensureConversation(snapshot,valid);if(next.messages[valid.messageId])throw new MessagingHistoryError("duplicate_message","Message already exists in local history.");assertCapacity(next.messages,"messages");return{...next,messages:{...next.messages,[valid.messageId]:{message:valid,direction:"outgoing",state:"pending"}},dedupeIds:appendUnique(next.dedupeIds,valid.messageId)}},expectedRevision)},transitionOutgoing(partition,messageId,state,failureCode,expectedRevision){return commit(partition,(snapshot)=>{const old=snapshot.messages[messageId];if(!old||old.direction!=="outgoing"||old.state!=="pending")throw new MessagingHistoryError("invalid_transition","Only pending outgoing messages may transition.");if((state==="failed"||state==="ambiguous")&&(!failureCode||failureCode.length>128))throw new MessagingHistoryError("invalid_transition","A bounded failure code is required.");return{...snapshot,messages:{...snapshot.messages,[messageId]:{...old,state,...state==="failed"||state==="ambiguous"?{failureCode}:{}}}}},expectedRevision)},retryOutgoing(partition,messageId,expectedRevision){return commit(partition,(snapshot)=>{const old=snapshot.messages[messageId];if(!old||old.direction!=="outgoing"||old.state!=="pending"&&old.state!=="failed"&&old.state!=="ambiguous")throw new MessagingHistoryError("invalid_transition","Only pending, failed, or ambiguous outgoing messages may be retried.");if(old.state==="pending")return snapshot;return{...snapshot,messages:{...snapshot.messages,[messageId]:{...old,state:"pending"}}}},expectedRevision)},persistIncoming(partition,message,expectedRevision){return commit(partition,(snapshot)=>{const valid=assertIncomingForPartition(message,snapshot.identityKey);if(snapshot.dedupeIds.includes(valid.messageId))return snapshot;let next=ensureConversation(snapshot,valid);assertCapacity(next.messages,"messages");const conversation=next.conversations[valid.conversationId];next={...next,conversations:{...next.conversations,[valid.conversationId]:{...conversation,unreadCount:conversation.unreadCount+1}}};return{...next,messages:{...next.messages,[valid.messageId]:{message:valid,direction:"incoming",state:"received",receivedAt:timestamp()}},dedupeIds:appendUnique(next.dedupeIds,valid.messageId)}},expectedRevision)},reconcileRemoteMessage(partition,message,outgoingState,expectedRevision){return commit(partition,(snapshot)=>{const valid=assertMapAppMessageV1(message);const outgoing=valid.senderIdentityKey.toLowerCase()===snapshot.identityKey.toLowerCase();if(outgoing)assertOutgoingForPartition(valid,snapshot.identityKey);else assertIncomingForPartition(valid,snapshot.identityKey);const direction=outgoing?"outgoing":"incoming";const existing=snapshot.messages[valid.messageId];if(existing){if(existing.direction!==direction||JSON.stringify(existing.message)!==JSON.stringify(valid))throw new MessagingHistoryError("message_id_conflict","A restored message ID conflicts with local history.");if(!outgoing)return snapshot.dedupeIds.includes(valid.messageId)?snapshot:{...snapshot,dedupeIds:appendUnique(snapshot.dedupeIds,valid.messageId)};const nextState=mergeRemoteOutgoingState(existing.state,outgoingState);const failureCode=nextState==="ambiguous"?"delivery_unconfirmed":nextState==="failed"?existing.failureCode??"delivery_rejected":undefined;if(existing.state===nextState&&existing.failureCode===failureCode&&snapshot.dedupeIds.includes(valid.messageId))return snapshot;const nextEntry={...existing,state:nextState,...failureCode?{failureCode}:{}};if(!failureCode)delete nextEntry.failureCode;return{...snapshot,messages:{...snapshot.messages,[valid.messageId]:nextEntry},dedupeIds:appendUnique(snapshot.dedupeIds,valid.messageId)}}let next=ensureConversation(snapshot,valid);assertCapacity(next.messages,"messages");const historyMessage=outgoing?{message:valid,direction:"outgoing",state:outgoingState,...outgoingState==="ambiguous"?{failureCode:"delivery_unconfirmed"}:outgoingState==="failed"?{failureCode:"delivery_rejected"}:{}}:{message:valid,direction:"incoming",state:"received",receivedAt:timestamp()};if(!outgoing){const conversation=next.conversations[valid.conversationId];next={...next,conversations:{...next.conversations,[valid.conversationId]:{...conversation,unreadCount:conversation.unreadCount+1}}}}return{...next,messages:{...next.messages,[valid.messageId]:historyMessage},dedupeIds:appendUnique(next.dedupeIds,valid.messageId)}},expectedRevision)},persistQuarantine(partition,record2,expectedRevision){return commit(partition,(snapshot)=>{validateQuarantine(record2);if(!snapshot.quarantine[record2.rawId])assertCapacity(snapshot.quarantine,"quarantine");return{...snapshot,quarantine:{...snapshot.quarantine,[record2.rawId]:structuredClone(record2)}}},expectedRevision)},markRead(partition,conversationId,expectedRevision){return commit(partition,(snapshot)=>{const conversation=snapshot.conversations[conversationId];if(!conversation)throw new MessagingHistoryError("conversation_missing","Conversation does not exist.");return{...snapshot,conversations:{...snapshot.conversations,[conversationId]:{...conversation,unreadCount:0}}}},expectedRevision)},block(partition,block,expectedRevision){return commit(partition,(snapshot)=>{validateBlock(block);if(!snapshot.blocks[block.identityKey.toLowerCase()])assertCapacity(snapshot.blocks,"blocks");return{...snapshot,blocks:{...snapshot.blocks,[block.identityKey.toLowerCase()]:structuredClone(block)}}},expectedRevision)},unblock(partition,key,expectedRevision){return commit(partition,(snapshot)=>{assertCompressedIdentityKey(key,"block identity");const blocks={...snapshot.blocks};delete blocks[key.toLowerCase()];return{...snapshot,blocks}},expectedRevision)},upsertContact(partition,contact,expectedRevision){return commit(partition,(snapshot)=>{validateContact(contact);const key=contact.identityKey.toLowerCase();const current=snapshot.contacts[key];if(current&&JSON.stringify(current)!==JSON.stringify(contact))throw new MessagingHistoryError("contact_immutable","Contact snapshots are immutable; record a key change instead.");if(!current)assertCapacity(snapshot.contacts,"contacts");return current?snapshot:{...snapshot,contacts:{...snapshot.contacts,[key]:structuredClone(contact)}}},expectedRevision)},recordKeyChange(partition,record2,expectedRevision){return commit(partition,(snapshot)=>{validateKeyChange(record2);if(snapshot.keyChanges.length>=MESSAGING_HISTORY_MAX_RECORDS)throw new MessagingHistoryError("history_full","Messaging history key-change capacity is full.");return{...snapshot,keyChanges:[...snapshot.keyChanges,structuredClone(record2)]}},expectedRevision)},acceptKeyChange(partition,conversationId,newIdentityKey,expectedRevision){return commit(partition,(snapshot)=>{assertCompressedIdentityKey(newIdentityKey,"new identity key");let found=false;const changes=snapshot.keyChanges.map((change)=>{if(change.conversationId===conversationId&&change.newIdentityKey.toLowerCase()===newIdentityKey.toLowerCase()&&!change.acceptedAt){found=true;return{...change,acceptedAt:timestamp()}}return change});if(!found)throw new MessagingHistoryError("key_change_missing","No unaccepted matching key change exists.");return{...snapshot,keyChanges:changes}},expectedRevision)},deleteMessages(partition,messageIds,expectedRevision){const ids=new Set(messageIds);if(ids.size===0)return commit(partition,(snapshot)=>snapshot,expectedRevision);return commit(partition,(snapshot)=>removeMessages(snapshot,ids),expectedRevision)},deleteConversation(partition,conversationId,expectedRevision){return commit(partition,(snapshot)=>{const removed=removeMessages(snapshot,new Set(Object.entries(snapshot.messages).filter(([,entry])=>entry.message.conversationId===conversationId).map(([id])=>id)));if(!removed.conversations[conversationId])return removed;const conversations={...removed.conversations};delete conversations[conversationId];return{...removed,conversations}},expectedRevision)},async replaceImportedEnvelope(partition,envelope,expectedRevision){assertPartition(partition);const cacheToken=cache.capture(partition.identityFingerprint);const imported=parseMessagingHistoryEnvelope(envelope);if(imported.identityFingerprint!==partition.identityFingerprint)throw new MessagingHistoryError("identity_mismatch","Import belongs to another wallet identity.");const validSnapshot=await crypto2.decrypt(imported.ciphertextBase64,partition.identityKey);return commit(partition,(snapshot)=>({...validSnapshot,createdAt:snapshot.createdAt,updatedAt:timestamp()}),expectedRevision,cacheToken)},clearSessionCache(identityFingerprint2){cache.clear(identityFingerprint2)}}}function removeMessages(snapshot,messageIds){const removed=new Set;const affectedConversations=new Set;const messages={...snapshot.messages};for(const[messageId,entry]of Object.entries(snapshot.messages)){if(!messageIds.has(messageId))continue;removed.add(messageId);affectedConversations.add(entry.message.conversationId);delete messages[messageId]}if(removed.size===0)return snapshot;const conversations={...snapshot.conversations};for(const conversationId of affectedConversations){const conversation=conversations[conversationId];if(!conversation)continue;const remaining=Object.values(messages).filter((entry)=>entry.message.conversationId===conversationId);if(remaining.length===0){delete conversations[conversationId];continue}const latest=remaining.reduce((value,entry)=>maxActivityTimestamp(value,entry.message.sentAt),remaining[0].message.sentAt);const remainingIncoming=remaining.filter((entry)=>entry.direction==="incoming").length;conversations[conversationId]={...conversation,lastMessageAt:latest,unreadCount:Math.min(conversation.unreadCount,remainingIncoming)}}return{...snapshot,messages,conversations}}function createIndexedDbMessagingHistoryStorage(indexedDB){return{async get(fingerprint){const db=await openDb(indexedDB);try{return await transaction(db,"readonly",(store)=>store.get(fingerprint)).then((value)=>value===undefined?null:parseMessagingHistoryEnvelope(value))}finally{db.close()}},async compareAndSwap(fingerprint,expected,next){const db=await openDb(indexedDB);try{return await new Promise((resolve2,reject)=>{const tx=db.transaction(STORE,"readwrite");const store=tx.objectStore(STORE);const request=store.get(fingerprint);let committed=false;request.onsuccess=()=>{const current=request.result===undefined?null:parseMessagingHistoryEnvelope(request.result);if((current?.revision??null)!==expected)return;store.put(structuredClone(next));committed=true};request.onerror=()=>reject(request.error??new Error("IndexedDB request failed."));tx.oncomplete=()=>resolve2(committed);tx.onerror=()=>reject(tx.error??new Error("IndexedDB transaction failed."));tx.onabort=()=>reject(tx.error??new Error("IndexedDB transaction aborted."))})}finally{db.close()}}}}function resolveIndexedDb2(injected){const value=injected??globalThis.indexedDB;if(!value)throw new MessagingHistoryError("indexeddb_unavailable","IndexedDB is required for encrypted messaging history.");return value}function openDb(indexedDB){return new Promise((resolve2,reject)=>{const request=indexedDB.open("mapapp-messaging-v1",DB_VERSION);request.onupgradeneeded=()=>{if(!request.result.objectStoreNames.contains(STORE))request.result.createObjectStore(STORE,{keyPath:"identityFingerprint"})};request.onsuccess=()=>resolve2(request.result);request.onerror=()=>reject(request.error??new Error("Could not open messaging history IndexedDB."));request.onblocked=()=>reject(new Error("Messaging history IndexedDB upgrade is blocked."))})}function transaction(db,mode,requestFactory){return new Promise((resolve2,reject)=>{const tx=db.transaction(STORE,mode);const request=requestFactory(tx.objectStore(STORE));let result;request.onsuccess=()=>{result=request.result};request.onerror=()=>reject(request.error??new Error("IndexedDB request failed."));tx.oncomplete=()=>resolve2(result);tx.onerror=()=>reject(tx.error??new Error("IndexedDB transaction failed."));tx.onabort=()=>reject(tx.error??new Error("IndexedDB transaction aborted."))})}function assertPartition(value){assertCompressedIdentityKey(value.identityKey,"partition identity");if(value.identityFingerprint!==identityFingerprint(value.identityKey))throw new MessagingHistoryError("identity_mismatch","Invalid history partition.")}function assertOutgoingForPartition(message,identityKey){const valid=assertMapAppMessageV1(message);if(valid.senderIdentityKey.toLowerCase()!==identityKey.toLowerCase())throw new MessagingHistoryError("identity_mismatch","Outgoing message sender does not match the current wallet identity.");return valid}function assertIncomingForPartition(message,identityKey){const valid=assertMapAppMessageV1(message);if(valid.recipientIdentityKey.toLowerCase()!==identityKey.toLowerCase())throw new MessagingHistoryError("identity_mismatch","Incoming message recipient does not match the current wallet identity.");return valid}function mergeRemoteOutgoingState(current,remote){if(current==="sent"||remote==="sent")return"sent";return remote}function appendUnique(values,value){if(values.includes(value))return[...values];if(values.length>=MESSAGING_HISTORY_MAX_RECORDS)throw new MessagingHistoryError("history_full","Messaging history dedupe capacity is full.");return[...values,value]}function assertCapacity(value,name){if(Object.keys(value).length>=MESSAGING_HISTORY_MAX_RECORDS)throw new MessagingHistoryError("history_full",`Messaging history ${name} capacity is full.`)}function validateQuarantine(value){if(!value||typeof value!=="object"||typeof value.rawId!=="string"||!value.rawId||typeof value.senderIdentityKey!=="string"||typeof value.receivedAt!=="string"||typeof value.errorCode!=="string"||value.errorCode.length>128||typeof value.rawCiphertextBase64!=="string")throw new MessagingHistoryError("quarantine_invalid","Invalid quarantine record.");assertCompressedIdentityKey(value.senderIdentityKey,"quarantine sender");if(!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value.rawCiphertextBase64)||value.rawCiphertextBase64.length/4*3>16*1024)throw new MessagingHistoryError("quarantine_invalid","Quarantine ciphertext exceeds its limit.")}function validateBlock(value){assertCompressedIdentityKey(value.identityKey,"block identity");if(!Number.isFinite(Date.parse(value.blockedAt))||!["pending","synced","failed","unblock-pending"].includes(value.serverSync))throw new MessagingHistoryError("block_invalid","Invalid block state.")}function validateContact(value){assertCompressedIdentityKey(value.identityKey,"contact identity");assertCompressedIdentityKey(value.keySnapshot,"contact key snapshot");if(!Number.isFinite(Date.parse(value.observedAt)))throw new MessagingHistoryError("contact_invalid","Invalid contact snapshot.")}function validateKeyChange(value){assertCompressedIdentityKey(value.oldIdentityKey,"old identity");assertCompressedIdentityKey(value.newIdentityKey,"new identity");if(value.oldIdentityKey.toLowerCase()===value.newIdentityKey.toLowerCase()||!Number.isFinite(Date.parse(value.observedAt)))throw new MessagingHistoryError("key_change_invalid","Invalid key-change record.")}import{MessageBoxClient}from"@bsv/message-box-client";import{Utils as Utils7}from"@bsv/sdk";class MessageBoxTransportError extends Error{code;constructor(code,message){super(message);this.code=code;this.name="MessageBoxTransportError"}}class MapAppMessageBoxTransport{host;messageBox;originator;wallet;maxPlaintextBytes;maxEncryptedBytes;maxRawBodyBytes;maxPageResponseBytes;pageLimit;maxPages;client;constructor(options){this.host=normalizePinnedHost(options.host);this.messageBox=options.messageBox??MAPAPP_MESSAGE_BOX_NAME;if(!/^[a-z0-9][a-z0-9-]{0,127}$/.test(this.messageBox))throw new MessageBoxTransportError("invalid_message","Message box name is invalid.");this.wallet=options.wallet;this.originator=options.originator===undefined?undefined:normalizeOriginator(options.originator);this.maxPlaintextBytes=positiveSafeInteger(options.maxPlaintextBytes??DEFAULT_MESSAGE_PLAINTEXT_MAX_BYTES,"maxPlaintextBytes");this.maxEncryptedBytes=positiveSafeInteger(options.maxEncryptedBytes??this.maxPlaintextBytes+1024,"maxEncryptedBytes");this.maxRawBodyBytes=positiveSafeInteger(options.maxRawBodyBytes??Math.ceil(this.maxEncryptedBytes*4/3)+128,"maxRawBodyBytes");this.pageLimit=positiveSafeInteger(options.pageLimit??100,"pageLimit");this.maxPageResponseBytes=positiveSafeInteger(options.maxPageResponseBytes??this.maxRawBodyBytes*this.pageLimit+4096,"maxPageResponseBytes");this.maxPages=positiveSafeInteger(options.maxPages??100,"maxPages");this.client=(options.clientFactory??((input)=>new MessageBoxClient({host:input.host,walletClient:input.wallet,originator:input.originator})))({host:this.host,wallet:this.wallet,originator:this.originator})}async send(message){const valid=assertMapAppMessageV1(message,this.maxPlaintextBytes);const sender=await this.wallet.getPublicKey({identityKey:true},this.originator);assertCompressedIdentityKey(sender.publicKey,"sending wallet identity key");if(sender.publicKey.toLowerCase()!==valid.senderIdentityKey.toLowerCase())throw new MessageBoxTransportError("invalid_message","Message sender does not match the connected wallet identity key.");const result=await this.client.sendMessage({recipient:valid.recipientIdentityKey,messageBox:this.messageBox,messageId:valid.messageId,body:serializeMapAppMessageV1(valid,this.maxPlaintextBytes),skipEncryption:false},this.host);return{messageId:result.messageId}}async listRaw(){const pages=[];let offset=0;while(true){if(pages.length>=this.maxPages)throw new MessageBoxTransportError("invalid_page","MessageBox pagination exceeded the configured maximum.");const response=await this.client.authFetch.fetch(new URL("/listMessages",this.host).toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({messageBox:this.messageBox,offset,limit:this.pageLimit})});if(!response.ok)throw new MessageBoxTransportError("invalid_page",`MessageBox list failed with HTTP ${response.status}.`);const data=await readBoundedJson(response,this.maxPageResponseBytes);const page=parseRawPage(data,offset,this.pageLimit,this.maxRawBodyBytes);pages.push(page);if(!page.hasMore)return Object.freeze(pages);offset=page.nextOffset}}async decrypt(raw){return await this.decryptWithCounterparty(raw,raw.sender,raw.sender,null,"recipient")}async decryptArchived(record2){const raw={messageId:record2.messageId,sender:record2.sender,body:record2.body};if(record2.messageBox!==this.messageBox||record2.direction!=="inbound"&&record2.direction!=="outbound")return rejected(raw,"permanent",new MessageBoxTransportError("invalid_message","Archived message metadata is invalid."));try{assertCompressedIdentityKey(record2.sender,"archived message sender");assertCompressedIdentityKey(record2.recipient,"archived message recipient")}catch(cause){return rejected(raw,"permanent",cause instanceof Error?cause:new Error("Archived message metadata is invalid."))}const counterparty=record2.direction==="inbound"?record2.sender:record2.recipient;return await this.decryptWithCounterparty(raw,counterparty,record2.sender,record2.recipient,record2.direction==="inbound"?"recipient":"sender")}async decryptWithCounterparty(raw,counterparty,expectedSender,expectedRecipient,receiverRole){let ciphertext;try{const wrapper=parseEncryptedWrapper(raw.body,this.maxEncryptedBytes,this.maxRawBodyBytes);ciphertext=Utils7.toArray(wrapper.encryptedMessage,"base64")}catch(cause){return rejected(raw,"permanent",cause)}let decrypted;try{decrypted=await this.wallet.decrypt({protocolID:[...MESSAGEBOX_PROTOCOL],keyID:MESSAGEBOX_KEY_ID,counterparty,ciphertext},this.originator)}catch(cause){return rejected(raw,"transient",cause)}let message;try{const text=new TextDecoder("utf-8",{fatal:true}).decode(new Uint8Array(decrypted.plaintext));if(new TextEncoder().encode(text).byteLength>this.maxPlaintextBytes)throw new MessageBoxTransportError("message_too_large","Decrypted message exceeds the plaintext maximum.");let payload;try{payload=JSON.parse(text)}catch{throw new MessageBoxTransportError("invalid_message","Decrypted message is not JSON.")}message=assertMapAppMessageV1(payload,this.maxPlaintextBytes)}catch(cause){return rejected(raw,"permanent",cause)}let receiver;try{receiver=await this.wallet.getPublicKey({identityKey:true},this.originator)}catch(cause){return rejected(raw,"transient",cause)}try{assertCompressedIdentityKey(receiver.publicKey,"recipient wallet identity key");if(message.messageId!==raw.messageId)throw new MessageBoxTransportError("invalid_message","Message ID does not match MessageBox metadata.");if(message.senderIdentityKey.toLowerCase()!==expectedSender.toLowerCase())throw new MessageBoxTransportError("invalid_message","Message sender does not match MessageBox metadata.");if(expectedRecipient!==null&&message.recipientIdentityKey.toLowerCase()!==expectedRecipient.toLowerCase())throw new MessageBoxTransportError("invalid_message","Message recipient does not match archived metadata.");const walletIdentity=receiverRole==="recipient"?message.recipientIdentityKey:message.senderIdentityKey;if(walletIdentity.toLowerCase()!==receiver.publicKey.toLowerCase())throw new MessageBoxTransportError("invalid_message","Message does not belong to the connected wallet.");return Object.freeze({kind:"accepted",raw,message})}catch(cause){return rejected(raw,"permanent",cause)}}async acknowledge(messageIds){if(!Array.isArray(messageIds)||messageIds.length===0||messageIds.some((id)=>typeof id!=="string"||id.trim()===""))throw new MessageBoxTransportError("invalid_message","Acknowledgement requires one or more message IDs.");return await this.client.acknowledgeMessage({messageIds:[...messageIds],host:this.host})}async blockSender(senderIdentityKey){assertCompressedIdentityKey(senderIdentityKey,"blocked sender identity key");await this.client.setMessageBoxPermission({messageBox:this.messageBox,sender:senderIdentityKey,recipientFee:-1},this.host)}async unblockSender(senderIdentityKey){assertCompressedIdentityKey(senderIdentityKey,"unblocked sender identity key");await this.client.setMessageBoxPermission({messageBox:this.messageBox,sender:senderIdentityKey,recipientFee:0},this.host)}}function parseRawPage(value,expectedOffset,expectedLimit,maxRawBodyBytes){if(!value||typeof value!=="object"||Array.isArray(value))throw new MessageBoxTransportError("invalid_page","MessageBox page is not an object.");const page=value;if(page.status!=="success"||!Array.isArray(page.messages)||page.messages.length>expectedLimit||page.offset!==expectedOffset||page.limit!==expectedLimit||typeof page.hasMore!=="boolean"||page.nextOffset!==undefined&&!Number.isSafeInteger(page.nextOffset))throw new MessageBoxTransportError("invalid_page","MessageBox page metadata is malformed.");const nextOffset=page.nextOffset===undefined?expectedOffset+page.messages.length:page.nextOffset;if(nextOffset!==expectedOffset+page.messages.length||page.hasMore&&page.messages.length===0)throw new MessageBoxTransportError("invalid_page","MessageBox page offsets are not contiguous.");const messages=page.messages.map((item)=>parseRawMessage(item,maxRawBodyBytes));return Object.freeze({offset:page.offset,limit:page.limit,nextOffset,hasMore:page.hasMore,messages:Object.freeze(messages)})}function parseRawMessage(value,maxRawBodyBytes){if(!value||typeof value!=="object"||Array.isArray(value))throw new MessageBoxTransportError("invalid_message","MessageBox record is malformed.");const item=value;if(typeof item.messageId!=="string"||item.messageId.trim()===""||new TextEncoder().encode(item.messageId).byteLength>MAPAPP_MESSAGE_ID_MAX_BYTES||typeof item.sender!=="string"||typeof item.body!=="string")throw new MessageBoxTransportError("invalid_message","MessageBox record has invalid required fields.");assertCompressedIdentityKey(item.sender,"MessageBox sender");assertRawBodyBounded(item.body,maxRawBodyBytes);const createdAt=optionalTimestamp(item.created_at??item.createdAt,"createdAt");const updatedAt=optionalTimestamp(item.updated_at??item.updatedAt,"updatedAt");return Object.freeze({messageId:item.messageId,sender:item.sender,body:item.body,...createdAt?{createdAt}:{},...updatedAt?{updatedAt}:{}})}function parseEncryptedWrapper(body,maxEncryptedBytes,maxRawBodyBytes){assertRawBodyBounded(body,maxRawBodyBytes);let wrapper;try{wrapper=JSON.parse(body)}catch{throw new MessageBoxTransportError("invalid_message","MessageBox body is not an encrypted JSON wrapper.")}if(wrapper&&typeof wrapper==="object"&&!Array.isArray(wrapper)&&Object.keys(wrapper).length===1&&"message"in wrapper){const message=wrapper.message;if(typeof message==="string"){try{wrapper=JSON.parse(message)}catch{throw new MessageBoxTransportError("invalid_message","MessageBox envelope message is not encrypted JSON.")}}else{wrapper=message}}if(!wrapper||typeof wrapper!=="object"||Array.isArray(wrapper)||Object.keys(wrapper).length!==1||typeof wrapper.encryptedMessage!=="string")throw new MessageBoxTransportError("invalid_message","MessageBox body must be exactly { encryptedMessage: string }.");const encryptedMessage=wrapper.encryptedMessage;if(!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(encryptedMessage))throw new MessageBoxTransportError("invalid_message","Encrypted message is not canonical base64.");const bytes=Utils7.toArray(encryptedMessage,"base64");if(bytes.length===0||bytes.length>maxEncryptedBytes||Utils7.toBase64(bytes)!==encryptedMessage)throw new MessageBoxTransportError("message_too_large","Encrypted message exceeds the transport limit.");return{encryptedMessage}}function normalizePinnedHost(value){try{const parsed=new URL(value);if(parsed.protocol!=="https:"||parsed.username||parsed.password||parsed.pathname!=="/"||parsed.search||parsed.hash)throw new Error;return parsed.origin}catch{throw new MessageBoxTransportError("invalid_host","MessageBox host must be an HTTPS origin without a path.")}}function normalizeOriginator(value){const originator=value.trim().toLowerCase();if(originator.length===0||originator.length>250||originator.includes(":"))throw new MessageBoxTransportError("invalid_host","Wallet originator must be a canonical FQDN.");const labels=originator.split(".");if(labels.length<2||labels.some((label)=>!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)))throw new MessageBoxTransportError("invalid_host","Wallet originator must be a canonical FQDN.");return originator}function assertRawBodyBounded(body,maxRawBodyBytes){if(new TextEncoder().encode(body).byteLength>maxRawBodyBytes)throw new MessageBoxTransportError("message_too_large","MessageBox body exceeds the raw transport limit.")}function optionalTimestamp(value,field){if(value===undefined)return;if(typeof value!=="string"||new TextEncoder().encode(value).byteLength>64)throw new MessageBoxTransportError("invalid_message",`MessageBox ${field} is invalid.`);try{assertCanonicalIsoTimestamp(value,field)}catch{throw new MessageBoxTransportError("invalid_message",`MessageBox ${field} is invalid.`)}return value}async function readBoundedJson(response,maximum){const contentLength=response.headers.get("content-length");if(contentLength!==null&&(!/^\d+$/.test(contentLength)||Number(contentLength)>maximum))throw new MessageBoxTransportError("message_too_large","MessageBox page exceeds the response limit.");const bytes=await readResponseBytes(response,maximum);try{return JSON.parse(new TextDecoder("utf-8",{fatal:true}).decode(bytes))}catch{throw new MessageBoxTransportError("invalid_page","MessageBox returned invalid JSON.")}}async function readResponseBytes(response,maximum){if(!response.body){const bytes2=new Uint8Array(await response.arrayBuffer());if(bytes2.byteLength>maximum)throw new MessageBoxTransportError("message_too_large","MessageBox page exceeds the response limit.");return bytes2}const reader=response.body.getReader();const chunks=[];let length=0;try{while(true){const next=await reader.read();if(next.done)break;length+=next.value.byteLength;if(length>maximum)throw new MessageBoxTransportError("message_too_large","MessageBox page exceeds the response limit.");chunks.push(next.value)}}finally{reader.releaseLock()}const bytes=new Uint8Array(length);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.byteLength}return bytes}function positiveSafeInteger(value,field){if(!Number.isSafeInteger(value)||value<=0)throw new TypeError(`${field} must be a positive safe integer.`);return value}function rejected(raw,disposition,cause){return Object.freeze({kind:"rejected",raw,disposition,error:cause instanceof Error?cause:new Error("Message processing failed.")})}import{LIMITS,MessageBoxStoreClient,createFreeOnlyMessageBoxClient,sendOutboundOnce,syncHistory,syncPending}from"@jdr377/message-box-store";import{canonicalRecordKey,isIdentityKey,isUint64DecimalString,isRecordKey}from"@jdr377/message-box-store";var DATABASE="mapapp-message-history-replica-v1";var DATABASE_VERSION2=1;var SCOPES="scopes";var RECORDS="records";var STAGE_META="stage-meta";var STAGE_RECORDS="stage-records";var messagingReplicaValidation={normalizeScope,scopeKey,validateHistoryRecord,validateSnapshotId,validateUint64,validateEpoch,validateOpaque,cloneExpectedVersion,assertExpectedVersion,sameExpectedVersion,sameScope,sameRecord,incrementUint64,isDeleteEvent,isRecordKey};function createMessagingHistoryReplica(indexedDb){const factory=indexedDb??globalThis.indexedDB;if(!factory)throw new Error("IndexedDB is required for messaging history.");return{async readCoverage(scope){const normalized=normalizeScope(scope);return await withTransaction(factory,[SCOPES],"readonly",async(tx)=>{const row=await request(tx.objectStore(SCOPES).get(scopeKey(normalized)));return row?.coverage?structuredClone(row.coverage):null})},async beginSnapshot(input){const scope=normalizeScope(input.scope);const key=scopeKey(scope);const snapshotId=validateSnapshotId(input.snapshotId);const epoch=validateEpoch(input.epoch,"snapshot epoch");const expected=cloneExpectedVersion(input.expected);await withTransaction(factory,[SCOPES,STAGE_META],"readwrite",async(tx)=>{const scopes=tx.objectStore(SCOPES);const current=await request(scopes.get(key));assertExpectedVersion(current?.coverage??null,expected);const stageMeta=tx.objectStore(STAGE_META);const stageKey=[key,snapshotId];const existing=await request(stageMeta.get(stageKey));if(existing&&(existing.epoch!==epoch||!sameExpectedVersion(existing.expected,expected))){throw new Error("A different snapshot already uses this replica key.")}stageMeta.put({scopeKey:key,snapshotId,scope,epoch,expected})})},async stageSnapshotPage(input){const scope=normalizeScope(input.scope);const key=scopeKey(scope);const snapshotId=validateSnapshotId(input.snapshotId);const epoch=validateEpoch(input.epoch,"snapshot epoch");const records=input.records.map((record2)=>validateHistoryRecord(record2,scope));await withTransaction(factory,[STAGE_META,STAGE_RECORDS],"readwrite",async(tx)=>{const stageKey=[key,snapshotId];const meta=await request(tx.objectStore(STAGE_META).get(stageKey));if(!meta||meta.epoch!==epoch||!sameScope(meta.scope,scope))throw new Error("Replica snapshot was not started for this scope and epoch.");const store=tx.objectStore(STAGE_RECORDS);for(const record2 of records){const rowKey=[key,snapshotId,record2.recordKey];const existing=await request(store.get(rowKey));if(existing&&!sameRecord(existing.record,record2))throw new Error("Conflicting record appeared in one replica snapshot.");if(!existing)store.put({scopeKey:key,snapshotId,snapshotKey:`${key}\x00${snapshotId}`,recordKey:record2.recordKey,record:record2})}})},async discardSnapshot(input){const scope=normalizeScope(input.scope);const key=scopeKey(scope);const snapshotId=validateSnapshotId(input.snapshotId);await withTransaction(factory,[STAGE_META,STAGE_RECORDS],"readwrite",async(tx)=>{tx.objectStore(STAGE_META).delete([key,snapshotId]);await deleteStageRows(tx.objectStore(STAGE_RECORDS),key,snapshotId)})},async commitSnapshot(input){const scope=normalizeScope(input.scope);const key=scopeKey(scope);const snapshotId=validateSnapshotId(input.snapshotId);const epoch=validateEpoch(input.epoch,"snapshot epoch");const checkpoint=validateUint64(input.checkpoint,"snapshot checkpoint");return await withTransaction(factory,[SCOPES,RECORDS,STAGE_META,STAGE_RECORDS],"readwrite",async(tx)=>{const scopes=tx.objectStore(SCOPES);const scopeRow=await request(scopes.get(key));const stageMetaStore=tx.objectStore(STAGE_META);const meta=await request(stageMetaStore.get([key,snapshotId]));if(!meta||meta.epoch!==epoch||!sameScope(meta.scope,scope))throw new Error("Replica snapshot was not started for this scope and epoch.");assertExpectedVersion(scopeRow?.coverage??null,meta.expected);const stageStore=tx.objectStore(STAGE_RECORDS);const staged=await request(stageStore.index("snapshotKey").getAll(`${key}\x00${snapshotId}`));const recordStore=tx.objectStore(RECORDS);if(scopeRow?.coverage&&scopeRow.coverage.epoch!==epoch){await invalidateOwnerEpoch(tx,scope.owner,epoch)}else{await deleteScopeRecords(recordStore,key)}for(const row of staged)recordStore.put({scopeKey:key,owner:scope.owner,recordKey:row.recordKey,record:row.record});const prior=scopeRow?.coverage?.epoch===epoch?scopeRow.coverage.generation:"0";const coverage={scope,epoch,checkpoint,generation:incrementUint64(prior),continuation:null,complete:true};scopes.put({scopeKey:key,owner:scope.owner,scope,coverage});stageMetaStore.delete([key,snapshotId]);await deleteStageRows(stageStore,key,snapshotId);return structuredClone(coverage)})},async applyIncrementalPage(input){const scope=normalizeScope(input.scope);const key=scopeKey(scope);const epoch=validateEpoch(input.epoch,"incremental epoch");const checkpoint=validateUint64(input.checkpoint,"incremental checkpoint");const continuation=input.continuation===null?null:{cursor:validateOpaque(input.continuation.cursor,"replica cursor"),watermark:validateUint64(input.continuation.watermark,"replica watermark")};const expected=cloneExpectedVersion(input.expected);return await withTransaction(factory,[SCOPES,RECORDS],"readwrite",async(tx)=>{const scopes=tx.objectStore(SCOPES);const current=await request(scopes.get(key));const currentCoverage=current?.coverage??null;assertExpectedVersion(currentCoverage,expected);if(!currentCoverage||currentCoverage.epoch!==epoch)throw new Error("Incremental replica page does not match the current owner epoch.");const records=tx.objectStore(RECORDS);for(const item of input.records){if(isDeleteEvent(item)){if(!isRecordKey(item.recordKey))throw new Error("Invalid replica delete key.");validateUint64(item.sequence,"delete sequence");if(!Number.isFinite(Date.parse(item.deletedAt)))throw new Error("Invalid replica delete timestamp.");records.delete([key,item.recordKey])}else{const record2=validateHistoryRecord(item,scope);records.put({scopeKey:key,owner:scope.owner,recordKey:record2.recordKey,record:record2})}}const coverage={scope,epoch,checkpoint,generation:incrementUint64(currentCoverage.generation),continuation,complete:true};scopes.put({scopeKey:key,owner:scope.owner,scope,coverage});return structuredClone(coverage)})},async listRecords(owner,filter){const scope=normalizeScope({owner,filter:filter??{direction:null,messageBox:null,participant:null}});const key=scopeKey(scope);return await withTransaction(factory,[RECORDS],"readonly",async(tx)=>{const rows=await request(tx.objectStore(RECORDS).index("scopeKey").getAll(key));return rows.map((row)=>structuredClone(row.record))})}}}function openDatabase2(factory){return new Promise((resolve2,reject)=>{const open=factory.open(DATABASE,DATABASE_VERSION2);open.onupgradeneeded=()=>{const db=open.result;if(!db.objectStoreNames.contains(SCOPES)){const scopes=db.createObjectStore(SCOPES,{keyPath:"scopeKey"});scopes.createIndex("owner","owner",{unique:false})}if(!db.objectStoreNames.contains(RECORDS)){const records=db.createObjectStore(RECORDS,{keyPath:["scopeKey","recordKey"]});records.createIndex("scopeKey","scopeKey",{unique:false});records.createIndex("owner","owner",{unique:false})}if(!db.objectStoreNames.contains(STAGE_META))db.createObjectStore(STAGE_META,{keyPath:["scopeKey","snapshotId"]});if(!db.objectStoreNames.contains(STAGE_RECORDS)){const staged=db.createObjectStore(STAGE_RECORDS,{keyPath:["scopeKey","snapshotId","recordKey"]});staged.createIndex("snapshotKey","snapshotKey",{unique:false})}};open.onsuccess=()=>resolve2(open.result);open.onerror=()=>reject(open.error??new Error("Could not open messaging replica IndexedDB."));open.onblocked=()=>reject(new Error("Messaging replica IndexedDB upgrade is blocked."))})}async function withTransaction(factory,stores,mode,operation){const db=await openDatabase2(factory);try{return await new Promise((resolve2,reject)=>{const tx=db.transaction(stores,mode);let result;let operationFailed=false;operation(tx).then((value)=>{result=value},(cause)=>{operationFailed=true;try{tx.abort()}catch{reject(cause)}reject(cause)});tx.oncomplete=()=>{if(!operationFailed)resolve2(result)};tx.onerror=()=>reject(tx.error??new Error("Messaging replica transaction failed."));tx.onabort=()=>reject(tx.error??new Error("Messaging replica transaction aborted."))})}finally{db.close()}}function request(value){return new Promise((resolve2,reject)=>{value.onsuccess=()=>resolve2(value.result);value.onerror=()=>reject(value.error??new Error("Messaging replica IndexedDB request failed."))})}async function deleteScopeRecords(records,key){const keys=await request(records.index("scopeKey").getAllKeys(key));for(const recordKey of keys)records.delete(recordKey)}async function deleteStageRows(records,key,snapshotId){const keys=await request(records.index("snapshotKey").getAllKeys(`${key}\x00${snapshotId}`));for(const recordKey of keys)records.delete(recordKey)}async function invalidateOwnerEpoch(tx,owner,epoch){const scopes=tx.objectStore(SCOPES);const records=tx.objectStore(RECORDS);const existing=await request(scopes.index("owner").getAll(owner.toLowerCase()));for(const row of existing){if(row.coverage&&row.coverage.epoch!==epoch){scopes.delete(row.scopeKey);await deleteScopeRecords(records,row.scopeKey)}}}function normalizeScope(scope){if(!scope||typeof scope!=="object"||!isIdentityKey(scope.owner))throw new Error("Invalid messaging replica owner identity.");const filter=scope.filter;if(!filter||typeof filter!=="object")throw new Error("Invalid messaging replica filter.");if(filter.direction!==null&&filter.direction!=="inbound"&&filter.direction!=="outbound")throw new Error("Invalid messaging replica direction.");if(filter.messageBox!==null&&(typeof filter.messageBox!=="string"||!/^[a-z0-9][a-z0-9-]{0,127}$/.test(filter.messageBox)))throw new Error("Invalid messaging replica message box.");if(filter.participant!==null&&!isIdentityKey(filter.participant))throw new Error("Invalid messaging replica participant.");return{owner:scope.owner.toLowerCase(),filter:{direction:filter.direction,messageBox:filter.messageBox,participant:filter.participant?.toLowerCase()??null}}}function scopeKey(scope){return JSON.stringify([scope.owner,scope.filter.direction,scope.filter.messageBox,scope.filter.participant])}function validateHistoryRecord(value,scope){if(!value||typeof value!=="object"||!isRecordKey(value.recordKey)||typeof value.messageId!=="string"||value.messageId.length===0||typeof value.messageBox!=="string"||typeof value.direction!=="string"||value.direction!=="inbound"&&value.direction!=="outbound"||!isIdentityKey(value.sender)||!isIdentityKey(value.recipient)||typeof value.body!=="string"||typeof value.bodyHash!=="string"||!/^([0-9a-f]{64})$/.test(value.bodyHash)||typeof value.deliveryState!=="string"||!["prepared","received","unknown","accepted","failed"].includes(value.deliveryState)||!isUint64DecimalString(value.revision)||!isUint64DecimalString(value.changeSequence)||!Number.isFinite(Date.parse(value.createdAt))||!Number.isFinite(Date.parse(value.archivedAt))){throw new Error("Invalid encrypted messaging replica record.")}if(value.owner!==undefined&&(!isIdentityKey(value.owner)||value.owner.toLowerCase()!==scope.owner))throw new Error("Messaging replica record belongs to another wallet identity.");if(value.direction!==scope.filter.direction&&scope.filter.direction!==null)throw new Error("Messaging replica record direction does not match its scope.");if(value.messageBox!==scope.filter.messageBox&&scope.filter.messageBox!==null)throw new Error("Messaging replica record box does not match its scope.");if(scope.filter.participant!==null&&value.sender.toLowerCase()!==scope.filter.participant&&value.recipient.toLowerCase()!==scope.filter.participant)throw new Error("Messaging replica participant does not match its scope.");const expected=canonicalRecordKey({ownerIdentityKey:scope.owner,direction:value.direction,messageBox:value.messageBox,sender:value.sender,recipient:value.recipient,messageId:value.messageId});if(expected!==value.recordKey)throw new Error("Messaging replica record key does not match its authenticated fields.");return structuredClone(value)}function isDeleteEvent(value){return!!value&&typeof value==="object"&&"deletedAt"in value&&"sequence"in value&&!("body"in value)}function validateSnapshotId(value){if(typeof value!=="string"||value.length<1||value.length>256)throw new Error("Invalid replica snapshot ID.");return value}function validateOpaque(value,name){if(typeof value!=="string"||value.length===0||value.length>16384)throw new Error(`Invalid ${name}.`);return value}function validateUint64(value,name){if(!isUint64DecimalString(value))throw new Error(`Invalid ${name}.`);return value}function validateEpoch(value,name){if(typeof value!=="string"||!/^[A-Za-z0-9:_-]{1,128}$/.test(value))throw new Error(`Invalid ${name}.`);return value}function incrementUint64(value){const next=BigInt(value)+1n;if(next>18446744073709551615n)throw new Error("Messaging replica generation is exhausted.");return next.toString()}function cloneExpectedVersion(value){if(value===null)return null;validateEpoch(value.epoch,"expected epoch");validateUint64(value.checkpoint,"expected checkpoint");validateUint64(value.generation,"expected generation");if(value.continuation!==null){validateOpaque(value.continuation.cursor,"expected cursor");validateUint64(value.continuation.watermark,"expected watermark")}return structuredClone(value)}function assertExpectedVersion(actual,expected){const actualVersion=actual?{epoch:actual.epoch,checkpoint:actual.checkpoint,generation:actual.generation,continuation:actual.continuation}:null;if(!sameExpectedVersion(actualVersion,expected))throw new Error("Messaging replica version changed before commit.")}function sameExpectedVersion(left,right){if(left===null||right===null)return left===right;return left.epoch===right.epoch&&left.checkpoint===right.checkpoint&&left.generation===right.generation&&left.continuation?.cursor===right.continuation?.cursor&&left.continuation?.watermark===right.continuation?.watermark}function sameScope(left,right){return scopeKey(normalizeScope(left))===scopeKey(normalizeScope(right))}function sameRecord(left,right){return JSON.stringify(left)===JSON.stringify(right)}var MESSAGE_HISTORY_SYNC_PAGE_SIZE=50;var MESSAGE_HISTORY_PENDING_SYNC_MAX_PAGES=5;var MESSAGE_HISTORY_SYNC_MAX_PAGES=Math.max(Math.ceil(LIMITS.MAX_RECORDS_PER_OWNER/LIMITS.MAX_PAGE_RECORDS),Math.ceil(LIMITS.MAX_BYTES_PER_OWNER/LIMITS.MAX_PAGE_BYTES))+1;var MESSAGE_HISTORY_PENDING_MAX_MESSAGES=MESSAGE_HISTORY_SYNC_PAGE_SIZE*MESSAGE_HISTORY_PENDING_SYNC_MAX_PAGES;class DurableMessageHistory{messageBox;walletClient;historyClient;messageBoxClient;replica;constructor(options){this.messageBox=options.messageBox??MAPAPP_MESSAGE_BOX_NAME;this.walletClient=options.wallet;this.historyClient=options.historyClient??new MessageBoxStoreClient({walletClient:options.wallet,host:options.historyHost});this.messageBoxClient=options.messageBoxClient??createFreeOnlyMessageBoxClient({walletClient:options.wallet,host:options.messageBoxHost,originator:options.originator});this.replica=options.replica??createMessagingHistoryReplica(options.indexedDB)}async reconcile(owner,signal){const pending=await syncPending({messageBoxClient:this.messageBoxClient,historyClient:this.historyClient,messageBoxes:[this.messageBox],acknowledgeAfterArchive:true,pageSize:MESSAGE_HISTORY_SYNC_PAGE_SIZE,maxPages:MESSAGE_HISTORY_PENDING_SYNC_MAX_PAGES,maxMessages:MESSAGE_HISTORY_PENDING_MAX_MESSAGES,signal});if(pending.ownerIdentityKey.toLowerCase()!==owner.toLowerCase())throw new Error("Message history wallet identity changed during sync.");const previousRecords=await this.replica.listRecords(owner,{direction:null,messageBox:this.messageBox,participant:null});const sync=await syncHistory({owner,historyClient:this.historyClient,localReplica:this.replica,filter:{messageBox:this.messageBox},maxPages:MESSAGE_HISTORY_SYNC_MAX_PAGES,signal});const records=await this.replica.listRecords(owner,{direction:null,messageBox:this.messageBox,participant:null});const currentRecordKeys=new Set(records.map((record2)=>record2.recordKey));const removedMessageIds=sync.status==="complete"?Array.from(new Set(previousRecords.filter((record2)=>!currentRecordKeys.has(record2.recordKey)).map((record2)=>record2.messageId))):[];const pendingFailures=pending.outcomes.filter((outcome)=>outcome.archive==="failed"||outcome.archive==="rejected"||outcome.acknowledgement==="failed"||outcome.acknowledgement==="pending").length;return{pending,sync,records,pendingFailures,removedMessageIds}}async send(owner,message){const authenticatedOwner=await this.messageBoxClient.getIdentityKey();if(authenticatedOwner.toLowerCase()!==owner.toLowerCase()||message.senderIdentityKey.toLowerCase()!==owner.toLowerCase()){throw new Error("The connected wallet identity does not match the pending message.")}return await sendOutboundOnce({wallet:this.walletClient,messageBoxClient:this.messageBoxClient,historyClient:this.historyClient,recipient:message.recipientIdentityKey,messageBox:this.messageBox,messageId:message.messageId,plaintext:serializeMapAppMessageV1(message)})}async deleteMessages(owner,conversationId,messageIds){const requested=new Set(messageIds);return await this.deleteMatching(owner,requested,(record2)=>{if(!requested.has(record2.messageId))return false;try{return conversationIdForIdentityKeys(record2.sender,record2.recipient)===conversationId}catch{return false}})}async deleteConversation(owner,conversationId,localMessageIds){return await this.deleteMatching(owner,new Set(localMessageIds),(record2)=>{try{return conversationIdForIdentityKeys(record2.sender,record2.recipient)===conversationId}catch{return false}})}async deleteMatching(owner,requestedMessageIds,selectRecord){const before=await this.reconcile(owner);const isOwnerRecord=(record2)=>record2.messageBox===this.messageBox&&(record2.direction==="inbound"?record2.recipient.toLowerCase()===owner.toLowerCase():record2.sender.toLowerCase()===owner.toLowerCase());const targets=before.records.filter((record2)=>isOwnerRecord(record2)&&selectRecord(record2));const targetIds=new Set([...requestedMessageIds,...targets.map((record2)=>record2.messageId)]);if(before.sync.status!=="complete"||before.pendingFailures>0){return{deletedMessageIds:[],failedMessageIds:[...targetIds],failureMessage:"Message history is still syncing. Retry deletion after sync completes."}}const failures=new Map;for(const record2 of targets){try{await this.historyClient.deleteRecord({recordKey:record2.recordKey})}catch{failures.set(record2.messageId,"Could not delete this message from history. Retry when online.")}}let failedMessageIds=[...failures.keys()];let deletedMessageIds=[...targetIds].filter((messageId)=>!failures.has(messageId));let syncWarning;let convergedMessageIds=before.removedMessageIds;try{const after=await this.reconcile(owner);convergedMessageIds=[...convergedMessageIds,...after.removedMessageIds];if(after.sync.status==="complete"&&after.pendingFailures===0){const remainingIds=new Set(after.records.filter((record2)=>isOwnerRecord(record2)&&selectRecord(record2)).map((record2)=>record2.messageId));failedMessageIds=failedMessageIds.filter((messageId)=>remainingIds.has(messageId));deletedMessageIds=[...targetIds].filter((messageId)=>!remainingIds.has(messageId))}}catch{syncWarning="Deletion completed. This device will finish syncing the history change when the service is available."}return{deletedMessageIds:[...new Set([...deletedMessageIds,...convergedMessageIds])],failedMessageIds,...failedMessageIds.length>0?{failureMessage:"Could not delete one or more messages from history. Retry to continue."}:{},...syncWarning?{syncWarning}:{}}}}import{createAuthProof as createAuthProof4}from"@bsv/auth";var NOTIFICATION_MUTATION_VERSION="mapapp-notification-mutation-v2";var NOTIFICATION_CONTROLLER_LIFETIME_MS=5*60000;var NOTIFICATION_SELF_VERSION="mapapp-notification-self-v1";var NOTIFICATION_AUTH_PROTOCOL=[2,"mapapp notifications"];var NOTIFICATION_AUTH_ACTIONS={register:"register notification recipient",rotate:"rotate notification recipient wallet",revoke:"revoke notification recipient",self:"read own notification enrollment",list:"list own notifications",count:"count own notifications",markOne:"read one notification",markAll:"read all notifications"};function assertNotificationMutation(value){if(!value||typeof value!=="object"||Array.isArray(value))throw new TypeError("Notification mutation must be an object.");const row=value;const keys=["version","operation","bapId","walletIdentityKey","deployment","canonicalOrigin","network","clientOperationId","expectedBindingVersion","controllerExpiresAt"];if(Object.keys(row).length!==keys.length||keys.some((key)=>!(key in row)))throw new TypeError("Notification mutation has unsupported or missing fields.");if(row.version!==NOTIFICATION_MUTATION_VERSION||!["register","rotate","revoke"].includes(String(row.operation)))throw new TypeError("Notification mutation version or operation is invalid.");for(const key of["bapId","deployment","clientOperationId"]){const field=row[key];if(typeof field!=="string"||field.length<1||field.length>128||field.trim()!==field||[...field].some((character)=>character.charCodeAt(0)<=31||character.charCodeAt(0)===127))throw new TypeError(`${key} is invalid.`)}if(typeof row.walletIdentityKey!=="string"||!/^(02|03)[0-9a-fA-F]{64}$/.test(row.walletIdentityKey))throw new TypeError("Wallet identity key is invalid.");if(!Number.isSafeInteger(row.expectedBindingVersion)||Number(row.expectedBindingVersion)<0)throw new TypeError("Expected binding version is invalid.");if(typeof row.controllerExpiresAt!=="string"||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(row.controllerExpiresAt)||!Number.isFinite(Date.parse(row.controllerExpiresAt))||new Date(row.controllerExpiresAt).toISOString()!==row.controllerExpiresAt)throw new TypeError("Controller expiry is invalid.");if(row.network!=="mainnet"&&row.network!=="testnet")throw new TypeError("Notification network is invalid.");if(typeof row.canonicalOrigin!=="string")throw new TypeError("Notification origin is invalid.");const origin=new URL(row.canonicalOrigin);if(origin.protocol!=="https:"||origin.origin!==row.canonicalOrigin||origin.username||origin.password)throw new TypeError("Notification origin must be a canonical HTTPS origin.");return row}function assertNotificationSelfRequest(value){if(!value||typeof value!=="object"||Array.isArray(value))throw new TypeError("Notification self request must be an object.");const row=value;const keys=["version","walletIdentityKey","deployment","canonicalOrigin","network"];if(Object.keys(row).length!==keys.length||keys.some((key)=>!(key in row))||row.version!==NOTIFICATION_SELF_VERSION)throw new TypeError("Notification self request is invalid.");assertNotificationMutation({...row,version:NOTIFICATION_MUTATION_VERSION,operation:"register",bapId:"self",clientOperationId:"self",expectedBindingVersion:0,controllerExpiresAt:new Date(0).toISOString()});return row}var PUSH_VERSION="mapapp-push-v1";var PRIVATE_PUSH_AUTH_ACTION="signal private message activity";function assertPrivatePushHint(value){const row=value;if(!row||Object.keys(row).sort().join()!=="canonicalOrigin,deployment,network,recipientIdentityKey,signalId,version,walletIdentityKey"||row.version!==PUSH_VERSION||!/^(02|03)[0-9a-f]{64}$/.test(row.recipientIdentityKey)||!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(row.signalId))throw new TypeError("push_hint_invalid");assertNotificationSelfRequest({version:NOTIFICATION_SELF_VERSION,walletIdentityKey:row.walletIdentityKey,deployment:row.deployment,canonicalOrigin:row.canonicalOrigin,network:row.network});return row}function serializePrivatePushHint(value){const row=assertPrivatePushHint(value);return new TextEncoder().encode(JSON.stringify({version:row.version,walletIdentityKey:row.walletIdentityKey,deployment:row.deployment,canonicalOrigin:row.canonicalOrigin,network:row.network,recipientIdentityKey:row.recipientIdentityKey,signalId:row.signalId}))}import{createAuthProof as createAuthProof3}from"@bsv/auth";class NotificationRecipientClientError extends Error{status;code;conflict;constructor(status,code,message,conflict){super(message);this.status=status;this.code=code;this.conflict=conflict;this.name="NotificationRecipientClientError"}}async function readConfig(fetcher,base,signal,expectedOrigin){const response=await fetcher(`${base}/notifications/auth-config`,{signal,headers:{accept:"application/json"}});const row=await responseBody2(response);const proof=row.proof;if(row.enabled===false)throw new NotificationRecipientClientError(503,"notifications_disabled","Notification enrollment is not enabled on this deployment.");const keys=["enabled","deployment","canonicalOrigin","network","proof"];const proofKeys=["enabled","verifierIdentityKey","protocol","actions","proofLifetimeMs"];if(Object.keys(row).length!==keys.length||keys.some((key)=>!(key in row))||row.enabled!==true||!proof||Object.keys(proof).length!==proofKeys.length||proofKeys.some((key)=>!(key in proof))||proof.enabled!==true||typeof proof.verifierIdentityKey!=="string"||!/^(02|03)[0-9a-fA-F]{64}$/.test(proof.verifierIdentityKey)||JSON.stringify(proof.protocol)!==JSON.stringify(NOTIFICATION_AUTH_PROTOCOL)||JSON.stringify(proof.actions)!==JSON.stringify(NOTIFICATION_AUTH_ACTIONS)||!Number.isSafeInteger(proof.proofLifetimeMs)||proof.proofLifetimeMs<1||typeof row.deployment!=="string"||typeof row.canonicalOrigin!=="string"||expectedOrigin&&row.canonicalOrigin!==expectedOrigin||row.network!=="mainnet"&&row.network!=="testnet")invalid();return row}async function responseBody2(response){let value;try{value=await response.json()}catch{invalid()}if(!value||typeof value!=="object"||Array.isArray(value))invalid();const row=value;if(!response.ok)throw new NotificationRecipientClientError(response.status,typeof row.code==="string"?row.code:"notification_request_failed",typeof row.message==="string"?row.message:"Notification enrollment request failed.",row.code==="notification_binding_version_mismatch"&&Number.isSafeInteger(row.currentVersion)&&Number(row.currentVersion)>0&&(row.bindingState==="active"||row.bindingState==="revoked")?{currentVersion:Number(row.currentVersion),bindingState:row.bindingState}:undefined);if(row.ok!==true&&!Object.hasOwn(row,"proof"))invalid();return row}function invalid(){throw new NotificationRecipientClientError(502,"notification_response_invalid","Notification enrollment response is invalid.")}async function signalPrivatePush(options){const fetcher=options.fetch??fetch;const configuration=await fetcher("/api/notifications/push/config",{cache:"no-store",signal:options.signal});const push=await configuration.json();if(options.signal.aborted)return{state:"aborted"};if(!configuration.ok||!push.enabled||!push.privateEnabled)return{state:"disabled"};const config=await readConfig(fetcher,"/api",options.signal,options.expectedOrigin);const raw=serializePrivatePushHint({version:PUSH_VERSION,walletIdentityKey:options.senderIdentityKey,recipientIdentityKey:options.recipientIdentityKey,signalId:crypto.randomUUID(),deployment:config.deployment,canonicalOrigin:config.canonicalOrigin,network:config.network});const proof=await createAuthProof4({wallet:options.wallet,counterparty:config.proof.verifierIdentityKey,protocol:config.proof.protocol,action:PRIVATE_PUSH_AUTH_ACTION,body:raw,windowMs:config.proof.proofLifetimeMs});if(options.signal.aborted)return{state:"aborted"};const encoded=btoa(String.fromCharCode(...new TextEncoder().encode(JSON.stringify(proof)))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");const response=await fetcher("/api/notifications/push/private-hint",{method:"POST",signal:options.signal,headers:{"content-type":"application/json","x-mapapp-brc138-proof":encoded},body:raw});const result=await response.json();return{state:response.ok&&result.ok===true?"accepted":"rejected",httpStatus:response.status}}import{LIMITS as LIMITS2}from"@jdr377/message-box-store";import{Database as Database2}from"bun:sqlite";function openAgentMessagingDatabase(filename,owner,originator,maxValueBytes){if(!/^(02|03)[0-9a-f]{64}$/u.test(owner))throw new Error("messaging_owner_invalid");const db=new Database2(filename,{create:true,strict:true});db.run("CREATE TABLE IF NOT EXISTS kv(kind TEXT NOT NULL,key TEXT NOT NULL,owner TEXT NOT NULL,value TEXT NOT NULL,PRIMARY KEY(kind,key))");const get=(kind,key)=>{const row=db.query("SELECT value FROM kv WHERE kind=? AND key=?").get(kind,key);return row?JSON.parse(row.value):null};const put=(kind,key,value)=>{const encoded=JSON.stringify(value);if(Buffer.byteLength(encoded)>maxValueBytes)throw new Error("messaging_cache_limit");db.run("INSERT INTO kv(kind,key,owner,value) VALUES(?,?,?,?) ON CONFLICT(kind,key) DO UPDATE SET value=excluded.value",[kind,key,owner,encoded])};const drop=(kind,key)=>{db.run("DELETE FROM kv WHERE kind=? AND key=?",[kind,key])};const binding=get("meta","binding");if(binding&&(binding.owner!==owner||binding.originator!==originator)){db.close();throw new Error("messaging_storage_binding_changed")}if(!binding)put("meta","binding",{owner,originator});const atomic=(work)=>db.transaction(work).immediate();const history={async get(key){return get("history",key)},async compareAndSwap(key,expected,next){return atomic(()=>{const prior=get("history",key);if((prior?.revision??null)!==expected)return false;put("history",key,next);return true})}};return{db,get,put,drop,atomic,history,close(){db.close()}}}function openAgentMessagingStorage(filename,owner,originator){const{db,get,put,drop,atomic,history,close}=openAgentMessagingDatabase(filename,owner,originator,LIMITS2.MAX_BYTES_PER_OWNER+1024*1024);const scopeKey2=(scope)=>{const normalized=messagingReplicaValidation.normalizeScope(scope);if(normalized.owner!==owner)throw new Error("messaging_storage_owner_changed");return{scope:normalized,key:messagingReplicaValidation.scopeKey(normalized)}};const state=(key)=>get("replica",key)??{coverage:null,records:[]};const stageKey=(key,id)=>JSON.stringify([key,messagingReplicaValidation.validateSnapshotId(id)]);const checkRecords=(records)=>{if(records.length>LIMITS2.MAX_RECORDS_PER_OWNER)throw new Error("messaging_cache_limit")};const replica={async readCoverage(input){return state(scopeKey2(input).key).coverage},async beginSnapshot(input){const{key}=scopeKey2(input.scope),id=stageKey(key,input.snapshotId),epoch=messagingReplicaValidation.validateEpoch(input.epoch,"snapshot epoch");const expected=messagingReplicaValidation.cloneExpectedVersion(input.expected);atomic(()=>{messagingReplicaValidation.assertExpectedVersion(state(key).coverage,expected);const prior=get("stage",id);if(prior&&(prior.epoch!==epoch||!messagingReplicaValidation.sameExpectedVersion(prior.expected,expected)))throw new Error("messaging_snapshot_conflict");if(!prior)put("stage",id,{epoch,expected,records:[]})})},async stageSnapshotPage(input){const{scope,key}=scopeKey2(input.scope),id=stageKey(key,input.snapshotId);const records=input.records.map((record2)=>messagingReplicaValidation.validateHistoryRecord(record2,scope));atomic(()=>{const stage=get("stage",id);if(!stage||stage.epoch!==input.epoch)throw new Error("messaging_snapshot_missing");const merged=new Map(stage.records.map((record2)=>[record2.recordKey,record2]));for(const record2 of records){const prior=merged.get(record2.recordKey);if(prior&&!messagingReplicaValidation.sameRecord(prior,record2))throw new Error("messaging_snapshot_conflict");merged.set(record2.recordKey,record2)}stage.records=[...merged.values()];checkRecords(stage.records);put("stage",id,stage)})},async discardSnapshot(input){const{key}=scopeKey2(input.scope);atomic(()=>drop("stage",stageKey(key,input.snapshotId)))},async commitSnapshot(input){const{scope,key}=scopeKey2(input.scope),id=stageKey(key,input.snapshotId);const epoch=messagingReplicaValidation.validateEpoch(input.epoch,"snapshot epoch"),checkpoint=messagingReplicaValidation.validateUint64(input.checkpoint,"snapshot checkpoint");return atomic(()=>{const prior=state(key),staged=get("stage",id);if(!staged||staged.epoch!==epoch)throw new Error("messaging_snapshot_missing");messagingReplicaValidation.assertExpectedVersion(prior.coverage,staged.expected);const previousEpoch=get("meta","epoch")?.epoch;if(previousEpoch&&previousEpoch!==epoch)db.run("DELETE FROM kv WHERE owner=? AND kind IN ('replica','stage')",[owner]);put("meta","epoch",{epoch});const coverage={scope,epoch,checkpoint,generation:messagingReplicaValidation.incrementUint64(prior.coverage?.epoch===epoch?prior.coverage.generation:"0"),continuation:null,complete:true};put("replica",key,{coverage,records:staged.records});drop("stage",id);return coverage})},async applyIncrementalPage(input){const{scope,key}=scopeKey2(input.scope),epoch=messagingReplicaValidation.validateEpoch(input.epoch,"incremental epoch");const checkpoint=messagingReplicaValidation.validateUint64(input.checkpoint,"incremental checkpoint");const continuation=input.continuation===null?null:{cursor:messagingReplicaValidation.validateOpaque(input.continuation.cursor,"replica cursor"),watermark:messagingReplicaValidation.validateUint64(input.continuation.watermark,"replica watermark")};return atomic(()=>{const current=state(key);messagingReplicaValidation.assertExpectedVersion(current.coverage,input.expected);if(!current.coverage||current.coverage.epoch!==epoch)throw new Error("messaging_epoch_changed");const records=new Map(current.records.map((record2)=>[record2.recordKey,record2]));for(const item of input.records){if(messagingReplicaValidation.isDeleteEvent(item)){if(!messagingReplicaValidation.isRecordKey(item.recordKey)||!Number.isFinite(Date.parse(item.deletedAt)))throw new Error("messaging_delete_invalid");messagingReplicaValidation.validateUint64(item.sequence,"delete sequence");records.delete(item.recordKey)}else{const record2=messagingReplicaValidation.validateHistoryRecord(item,scope);records.set(record2.recordKey,record2)}}const rows=[...records.values()];checkRecords(rows);const coverage={scope,epoch,checkpoint,generation:messagingReplicaValidation.incrementUint64(current.coverage.generation),continuation,complete:true};put("replica",key,{coverage,records:rows});return coverage})},async listRecords(requestedOwner,filter){return state(scopeKey2({owner:requestedOwner,filter:filter??{direction:null,messageBox:null,participant:null}}).key).records}};return{history,replica,close}}function createAgentMessagingRuntime(options){return createAgentMessagingExecutor({wallet:options.context.wallet,binding:options.binding,open:async()=>{const config=await fetchMessagingDirectoryConfig(options.fetch,`${options.deployment}/api`);if(!config.enabled||!config.developmentCharacterization||config.canonicalOrigin!==options.deployment||config.network!=="mainnet")throw new Error("agent_messaging_profile_unavailable");const bapId=await resolveBapId(options.context);if(!bapId)throw new Error("agent_messaging_identity_required");const provisional=createMessagingDirectoryClient({wallet:options.context.wallet,originator:options.binding.originator,bapContext:options.context,bapId,signWithBap:(_context,message)=>signWithBap(options.context,message),profile:null,config,fetch:options.fetch,baseUrl:`${options.deployment}/api`});const profile=await provisional.profileSnapshot(bapId);const directory=createMessagingDirectoryClient({wallet:options.context.wallet,originator:options.binding.originator,bapContext:options.context,bapId,signWithBap:(_context,message)=>signWithBap(options.context,message),profile,config,fetch:options.fetch,baseUrl:`${options.deployment}/api`});let pinned;const storage=openAgentMessagingStorage(join(options.directory,"agent-messaging.sqlite"),options.binding.expectedIdentityKey,options.binding.originator);const history=createMessagingHistoryStore({wallet:options.context.wallet,originator:options.binding.originator,storage:storage.history});const coordinator=new MessagingCoordinator({wallet:options.context.wallet,bapId,originator:options.binding.originator,history,transport:new MapAppMessageBoxTransport({wallet:options.context.wallet,host:config.messageBoxHost,originator:options.binding.originator}),durableHistory:new DurableMessageHistory({wallet:options.context.wallet,historyHost:config.historyHost,messageBoxHost:config.messageBoxHost,originator:options.binding.originator,replica:storage.replica}),directory:{...directory,lookup:async(id)=>{const binding=await directory.lookup(id);if(pinned?.bapId===id&&binding.binding.walletIdentityKey!==pinned.identity)throw new Error("messaging_recipient_binding_changed");return binding}},privateMessagingReady:()=>config.enabled&&config.developmentCharacterization,automaticPolling:false});try{await coordinator.start()}catch(cause){coordinator.disconnect();storage.close();throw cause}return{coordinator,pinRecipient(id,identity){pinned={bapId:id,identity}},clearRecipient(){pinned=undefined},async notifySent(recipientIdentityKey){const fetcher=Object.assign(async(resource,init)=>{const target=new URL(typeof resource==="string"?resource:resource instanceof URL?resource.href:resource.url,options.deployment);if(target.origin!==options.deployment||!["/api/notifications/push/config","/api/notifications/auth-config","/api/notifications/push/private-hint"].includes(target.pathname)||target.search||target.hash||target.username||target.password)throw new Error("agent_push_target_invalid");return options.fetch(target.href,init)},{preconnect:options.fetch.preconnect});return signalPrivatePush({wallet:options.context.wallet,senderIdentityKey:options.binding.expectedIdentityKey,recipientIdentityKey,expectedOrigin:options.deployment,fetch:fetcher,signal:AbortSignal.timeout(5000)})},close(){coordinator.disconnect();history.clearSessionCache();storage.close()}}}})}function parseParticipantConnection(value){const record2=object3(value);return participantConnectionSchema.parse(record2?.connection??value)}async function readParticipantConnection(directory,connectionFile){const metadata=await readBoundedJson2(join2(directory,"wallet.json"));if(connectionFile){const retained=parseParticipantConnection(await readBoundedJson2(resolve2(connectionFile)));if(object3(metadata)?.connection!==undefined&&JSON.stringify(parseParticipantConnection(metadata))!==JSON.stringify(retained))throw new Error("participant_connection_changed");return retained}if(object3(metadata)?.connection!==undefined)return parseParticipantConnection(metadata);return parseParticipantConnection(await readBoundedJson2(join2(directory,"participant-authority.json")))}var participantMetadataSchema=z2.object({transport:z2.literal("direct_wallet_node").optional(),identityKey:z2.string().regex(/^(02|03)[0-9a-f]{64}$/u),storageIdentityKey:z2.string().min(1).max(512),connection:participantConnectionSchema.optional(),chain:z2.literal("main"),servicesBaseUrl:z2.string().url()});var participantExternalMetadataSchema=z2.strictObject({connection:participantConnectionSchema.optional(),transport:z2.literal("brc100_http"),identityKey:z2.string().regex(/^(02|03)[0-9a-f]{64}$/u),chain:z2.literal("main"),servicesBaseUrl:z2.string().url(),walletEndpoint:z2.string().url()});async function openAgentParticipantRuntime(options){const directory=resolve2(options.walletDirectory);const initial=await readParticipantConnection(directory,options.connectionFile);const raw=await readBoundedJson2(join2(directory,"wallet.json"));const external=object3(raw)?.transport==="brc100_http";const meta=external?participantExternalMetadataSchema.parse(raw):participantMetadataSchema.parse(raw);const binding={connectionId:initial.connectionId,callerId:initial.callerId,originator:initial.originator,expectedIdentityKey:meta.identityKey,expectedNetwork:"mainnet",route:external?"existing_brc100":"agent_environment",declaredAuthority:external?"provider":"full_root_authority"};const proofProfile=external?{transport:"brc100_http",authority:"provider",qualification:"participant_release",connectionReceipt:"mapapp-705.32"}:{transport:"direct_wallet_node",authority:"full_root_authority",qualification:"participant_release",connectionReceipt:"mapapp-705.31"};const services=new URL(meta.servicesBaseUrl);if(services.protocol!=="https:"||services.username||services.password||services.search||services.hash||services.pathname!=="/api/bitcoin/wallet-services")throw new Error("participant_services_configuration_invalid");for(const file of external?["agent-intents.sqlite"]:["root.wif","wallet.sqlite","agent-intents.sqlite"]){const info=await stat(join2(directory,file));if(!info.isFile()||info.size===0||file==="root.wif"&&info.size>128)throw new Error("participant_existing_wallet_required")}const owned=openAgentLocalIntentRepository(join2(directory,"agent-intents.sqlite"));let runtime;try{if("walletEndpoint"in meta){runtime={wallet:createParticipantHTTPWallet(meta.walletEndpoint,initial.originator,new URL(meta.servicesBaseUrl).origin),destroy:async()=>{}}}else{const root=PrivateKey2.fromWif((await readFile(join2(directory,"root.wif"),"utf8")).trim());if(root.toPublicKey().toString()!==meta.identityKey)throw new Error("participant_wallet_identity_changed");runtime=await createNodeWallet({chain:"main",privateKey:root,storageIdentityKey:meta.storageIdentityKey,servicesBaseUrl:meta.servicesBaseUrl,storage:{provider:"bun-sqlite",filename:join2(directory,"wallet.sqlite")},backups:[],skipInitialMonitor:true,feeModel:{model:"sat/kb",value:100}})}}catch(error2){owned.close();throw error2}try{const inspector=createAgentWalletConnectionInspector({wallet:runtime.wallet,binding});const connected=await inspector.inspect(initial.callerId);if(connected.state!=="ready")throw new Error(connected.reason);const context=createContext(runtime.wallet,{services:runtime.services??new OneSatServices("main",meta.servicesBaseUrl),chain:"main",dataDir:directory});const store=createLocalIntentStore({wallet:runtime.wallet,originator:initial.originator,repository:owned.repository,indexedRecords:true});const intakeFetch=Object.assign((resource,init)=>{const headers=new Headers(init?.headers);headers.set("origin",services.origin);return fetch(resource,{...init,headers,redirect:"error"})},{preconnect:fetch.preconnect});async function identityObserved(bapId){const result=await readPublicJson(services.origin+"/api/bitcoin/bap/identity/"+encodeURIComponent(bapId));return result.ok===true&&result.observed===true&&result.bapId===bapId}const client=createTransactionIntakeClient({wallet:runtime.wallet,baseUrl:services.origin+"/api/transaction-intakes",fetch:intakeFetch});const lockExecutor=createAgentLockExecutor({context,binding,deployment:services.origin,store,relay:(txid2,bytes)=>relaySignedBeef({relayUrl:services.origin+"/api/bitcoin/relay",action:"unlock-locks",txid:txid2,bytes,fetch:intakeFetch}),recoverEvidence:(txid2)=>readPublicBytes(services.origin+"/api/bitcoin/beef/"+txid2,2*1024*1024)});const paymentExecutor=createAgentPaymentExecutor({context,binding,deployment:services.origin,store,client,proofProfile,ready:async()=>{const[stack,auth]=await Promise.all([readPublicJson(services.origin+"/api/bitcoin/status"),readPublicJson(services.origin+"/api/transaction-intakes/auth-config")]);return stack.ok===true&&stack.network==="main"&&auth.enabled===true}});const identityExecutor=createAgentIdentityExecutor({wallet:runtime.wallet,binding,deployment:services.origin,store,client,bapId:()=>computeBapId(context),published:()=>resolveBapId2(context),observed:identityObserved,ready:async()=>{const[stack,sponsor]=await Promise.all([readPublicJson(services.origin+"/api/bitcoin/status"),readPublicJson(services.origin+"/api/freeposting/status")]);return stack.ok===true&&stack.network==="main"&&object3(sponsor.freeposting)?.available===true},challenge:async(hash)=>{const result=await readPublicJson(services.origin+"/api/freeposting/challenge?payloadHash="+hash);if(result.ok!==true||!object3(result.challenge))throw new Error("identity_challenge_unavailable");return result.challenge},invoke:(funding)=>executeSponsoredIdentity(context,funding)});const executor=createAgentPaidPostExecutor({wallet:runtime.wallet,binding,deployment:services.origin,store,client:createTransactionIntakeClient({wallet:runtime.wallet,baseUrl:`${services.origin}/api/transaction-intakes`,fetch:intakeFetch}),proofProfile,gates:async()=>{const[current,auth,bapId]=await Promise.all([readPublicJson(`${services.origin}/api/bitcoin/status`),readPublicJson(`${services.origin}/api/transaction-intakes/auth-config`),resolveBapId2(context)]);const freshness=current.freshness;const health=current.health;return{stackReady:current.ok===true&&current.network==="main",intakeReady:auth.enabled===true,paidRootEnabled:freshness?.lockOutputsEnabled===true&&Number.isSafeInteger(freshness.lockSats)&&freshness.lockSats>0&&Number.isSafeInteger(health?.height)&&health.height>0,identityReady:!!bapId&&await identityObserved(bapId),freshnessLockSats:freshness?.lockSats,blockHeight:health?.height,freshnessDefaultDuration:freshness?.defaultDuration,freshnessDurations:freshness?.durations}},invoke:(input,metadata)=>executePaidRootPost(context,input,metadata)});const sponsoredExecutor=createAgentSponsoredPostExecutor({wallet:runtime.wallet,binding,deployment:services.origin,store,client:createTransactionIntakeClient({wallet:runtime.wallet,baseUrl:`${services.origin}/api/transaction-intakes`,fetch:intakeFetch}),gates:async()=>{const[current,auth,sponsor,bapId]=await Promise.all([readPublicJson(`${services.origin}/api/bitcoin/status`),readPublicJson(`${services.origin}/api/transaction-intakes/auth-config`),readPublicJson(`${services.origin}/api/freeposting/status`),resolveBapId2(context)]);return{stackReady:current.ok===true&&current.network==="main",intakeReady:auth.enabled===true,sponsorReady:object3(sponsor.freeposting)?.available===true,bapId:bapId&&await identityObserved(bapId)?bapId:null}},targetReady:async(input)=>{if(input.parentTxid===input.rootTxid){const result2=await readPublicJson(`${services.origin}/api/posts/${input.rootTxid}`);return result2.ok===true&&object3(result2.post)?.txid===input.rootTxid}const result=await readPublicJson(`${services.origin}/api/posts/${input.rootTxid}/comments/${input.parentTxid}/context`);return result.ok===true&&result.postTxid===input.rootTxid&&result.commentTxid===input.parentTxid},challenge:async(draftHash)=>{const result=await readPublicJson(`${services.origin}/api/freeposting/challenge?payloadHash=${draftHash}`);if(result.ok!==true||!object3(result.challenge))throw new Error("sponsor_challenge_unavailable");return result.challenge},invoke:(draft,funding,privateTag)=>executeSponsoredContribution(context,draft,funding,privateTag)});const socialExecutor=createAgentSocialActionExecutor({wallet:runtime.wallet,binding,deployment:services.origin,store,client:createTransactionIntakeClient({wallet:runtime.wallet,baseUrl:`${services.origin}/api/transaction-intakes`,fetch:intakeFetch}),proofProfile,gates:async()=>{const[current,auth,bapId]=await Promise.all([readPublicJson(`${services.origin}/api/bitcoin/status`),readPublicJson(`${services.origin}/api/transaction-intakes/auth-config`),resolveBapId2(context)]);return{stackReady:current.ok===true&&current.network==="main",intakeReady:auth.enabled===true,identityReady:!!bapId}},targetReady:async(input)=>{if("targetBapId"in input){if(input.targetBapId===await resolveBapId2(context))return false;const value2=await readPublicJson(`${services.origin}/api/bitcoin/bap/identity/${input.targetBapId}`);return value2.ok===true&&value2.observed===true&&value2.bapId===input.targetBapId}const rootTxid=input.kind==="reply"?input.rootTxid:input.targetTxid;const fetched=await readPublicJson(`${services.origin}/api/agent/v1/fetch?txid=${rootTxid}&include=content`);const root=Array.isArray(fetched.results)?object3(fetched.results[0]):null;if(root?.txid!==rootTxid||root.eligibility!=="eligible")return false;if(input.kind!=="reply"||input.parentTxid===input.rootTxid)return true;const value=await readPublicJson(`${services.origin}/api/posts/${input.rootTxid}/comments/${input.parentTxid}/context`);return value.ok===true&&value.postTxid===input.rootTxid&&value.commentTxid===input.parentTxid},invoke:(input,privateTag)=>executeSocialV2Action({...context},input,privateTag),targetState:async(input)=>{const actor=await resolveBapId2(context);if(!actor||input.kind==="reply")throw new Error("social_state_unavailable");const path="targetBapId"in input?`/api/identities/${encodeURIComponent(input.targetBapId)}/follow?followerIdentity=${encodeURIComponent(actor)}`:`/api/posts/${input.targetTxid}/grow?actorBapId=${encodeURIComponent(actor)}`;const state=await readPublicJson(`${services.origin}${path}`);if(state.ok!==true||typeof state.active!=="boolean"||!(state.pendingTxid===null||typeof state.pendingTxid==="string"))throw new Error("social_state_unavailable");return{active:state.active,pendingTxid:state.pendingTxid}}});async function retainWalletBackup(family){if(external)return;if(!("storage"in runtime))throw new Error("participant_storage_unavailable");const archive=await exportWalletDataZip(runtime.storage,{identityKey:meta.identityKey,chain:"main"},{maxChunks:20});await writeFile(join2(directory,"wallet-data-"+family+"-"+crypto.randomUUID()+".zip"),archive.bytes,{flag:"wx",mode:384})}const execute=async(caller,operationId,signal)=>{const outcome=await executor.execute(caller,operationId,signal);if(!external&&(outcome.kind==="execution"||outcome.kind==="reconciled"||outcome.kind==="recovery_required")){try{await retainWalletBackup("participant-mcp")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable"}}}return outcome};const executeSponsored=async(caller,operationId,signal)=>{const outcome=await sponsoredExecutor.execute(caller,operationId,signal);if(!external&&(outcome.kind==="execution"||outcome.kind==="reconciled"||outcome.kind==="recovery_required")){try{await retainWalletBackup("sponsored-mcp")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable"}}}return outcome};const executeSocial=async(caller,operationId,signal)=>{const outcome=await socialExecutor.execute(caller,operationId,signal);if(!external&&(outcome.kind==="execution"||outcome.kind==="reconciled"||outcome.kind==="recovery_required")){try{await retainWalletBackup("social-mcp")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable"}}}return outcome};const executePayment=async(caller,operationId,signal)=>{const outcome=await paymentExecutor.execute(caller,operationId,signal);if(!external&&(outcome.kind==="execution"||outcome.kind==="reconciled"||outcome.kind==="recovery_required")){try{await retainWalletBackup("payment-mcp")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable"}}}return outcome};const messagingExecutor=createAgentMessagingRuntime({context,directory,deployment:services.origin,fetch:intakeFetch,binding});const readiness=async(caller)=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return{kind:"blocked",reason:connection.reason};const bapId=await computeBapId(context),published=await resolveBapId2(context);const observed=await identityObserved(bapId).catch(()=>false);const[stack,auth,sponsor]=await Promise.all([readPublicJson(services.origin+"/api/bitcoin/status").catch(()=>null),readPublicJson(services.origin+"/api/transaction-intakes/auth-config").catch(()=>null),readPublicJson(services.origin+"/api/freeposting/status").catch(()=>null)]);return{kind:"readiness",route:binding.route,authority:binding.declaredAuthority,wallet:{identityKey:meta.identityKey,network:"mainnet",bapId},identity:{walletPublished:published===bapId,publicObserved:observed},services:{stackReady:stack?.ok===true&&stack.network==="main",intakeReady:auth?.enabled===true,sponsorReady:object3(sponsor?.freeposting)?.available===true},delegation:"user_and_agent",permissions:"wallet_provider",funding:external?"provider_managed":"not_checked",next:published===bapId&&observed?"prepare_authorized_action":"prepare_sponsored_identity"}};const receive=async(caller,sync)=>{const connection=await inspector.inspect(caller);if(connection.state!=="ready")return{kind:"blocked",reason:connection.reason};if(external)return{kind:"blocked",reason:"receive_is_provider_owned"};if(sync)return{kind:"receive_synced",result:await syncAddresses.execute(context,{count:1})};return{kind:"receive_addresses",result:await deriveDepositAddresses.execute(context,{count:1}),funding:"not_checked"}};const executeIdentity=async(caller,operationId,signal)=>{const outcome=await identityExecutor.execute(caller,operationId,signal);if(!external&&(outcome.kind==="reconciled"||outcome.kind==="recovery_required")){try{await retainWalletBackup("identity")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable",retryAllowed:false}}}return outcome};const reconcileIdentity=async(caller,operationId)=>{const outcome=await identityExecutor.reconcile(caller,operationId);if(!external&&outcome.kind==="reconciled"){try{await retainWalletBackup("identity-recovered")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable",retryAllowed:false}}}return outcome};return{readiness,receive,identityExecutor:{...identityExecutor,execute:executeIdentity,reconcile:reconcileIdentity},executor:{...executor,execute},sponsoredExecutor:{...sponsoredExecutor,execute:executeSponsored},messagingExecutor,paymentExecutor:{...paymentExecutor,execute:executePayment},lockExecutor:{...lockExecutor,execute:async(caller,operationId,signal)=>{const outcome=await lockExecutor.execute(caller,operationId,signal);if(!external&&["execution","reconciled","recovery_required"].includes(outcome.kind)){try{await retainWalletBackup("lock-reclaim")}catch{return{kind:"recovery_required",operationId,reason:"wallet_backup_unavailable"}}}return outcome}},socialExecutor:{...socialExecutor,execute:executeSocial},callerId:initial.callerId,publicReads:{origin:services.origin},withReadSession:store.withReadSession,async close(){try{messagingExecutor.close();await runtime.destroy()}finally{owned.close()}}}}catch(error2){try{await runtime.destroy()}finally{owned.close()}throw error2}}function object3(value){return value&&typeof value==="object"&&!Array.isArray(value)?value:undefined}async function readBoundedJson2(path){if((await stat(path)).size>4096)throw new Error("participant_configuration_too_large");const bytes=await readFile(path);if(bytes.length>4096)throw new Error("participant_configuration_too_large");return JSON.parse(bytes.toString("utf8"))}async function readPublicJson(url){const bytes=await readPublicBytes(url,65536);const result=JSON.parse(new TextDecoder("utf-8",{fatal:true}).decode(bytes));if(!result||typeof result!=="object"||Array.isArray(result))throw new Error("participant_prerequisite_invalid");return result}async function readPublicBytes(url,maxBytes){const response=await fetch(url,{redirect:"error",signal:AbortSignal.timeout(15000)});if(!response.ok||!response.body){await response.body?.cancel();throw new Error("participant_prerequisite_unavailable")}const reader=response.body.getReader();const chunks=[];let size=0;try{while(true){const{done,value}=await reader.read();if(done)break;size+=value.length;if(size>maxBytes){await reader.cancel();throw new Error("participant_prerequisite_invalid")}chunks.push(value)}}finally{reader.releaseLock()}const bytes=new Uint8Array(size);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length}return bytes}
17
+ export{inspectWalletDataZip,exportWalletDataZip,restoreWalletDataZip,openAgentLocalIntentRepository,createParticipantHTTPWallet,parseParticipantConnection,readParticipantConnection,participantMetadataSchema,participantExternalMetadataSchema,openAgentParticipantRuntime};