@bsv/wallet-toolbox-client 2.4.3 → 2.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +58 -0
- package/README.md +43 -22
- package/out/index.client.cjs +33583 -0
- package/out/index.client.cjs.map +1 -0
- package/out/index.client.d.cts +10768 -0
- package/out/index.client.d.cts.map +1 -0
- package/out/index.client.d.mts +10767 -0
- package/out/index.client.d.mts.map +1 -0
- package/out/index.client.mjs +33377 -0
- package/out/index.client.mjs.map +1 -0
- package/out/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +59 -7
- package/out/src/CWIStyleWalletManager.d.ts +0 -517
- package/out/src/CWIStyleWalletManager.d.ts.map +0 -1
- package/out/src/CWIStyleWalletManager.js +0 -1594
- package/out/src/CWIStyleWalletManager.js.map +0 -1
- package/out/src/SetupClient.d.ts +0 -135
- package/out/src/SetupClient.d.ts.map +0 -1
- package/out/src/SetupClient.js +0 -226
- package/out/src/SetupClient.js.map +0 -1
- package/out/src/SetupWallet.d.ts +0 -103
- package/out/src/SetupWallet.d.ts.map +0 -1
- package/out/src/SetupWallet.js +0 -3
- package/out/src/SetupWallet.js.map +0 -1
- package/out/src/SimpleWalletManager.d.ts +0 -179
- package/out/src/SimpleWalletManager.d.ts.map +0 -1
- package/out/src/SimpleWalletManager.js +0 -360
- package/out/src/SimpleWalletManager.js.map +0 -1
- package/out/src/Wallet.d.ts +0 -254
- package/out/src/Wallet.d.ts.map +0 -1
- package/out/src/Wallet.js +0 -909
- package/out/src/Wallet.js.map +0 -1
- package/out/src/WalletAuthenticationManager.d.ts +0 -33
- package/out/src/WalletAuthenticationManager.d.ts.map +0 -1
- package/out/src/WalletAuthenticationManager.js +0 -126
- package/out/src/WalletAuthenticationManager.js.map +0 -1
- package/out/src/WalletLogger.d.ts +0 -65
- package/out/src/WalletLogger.d.ts.map +0 -1
- package/out/src/WalletLogger.js +0 -177
- package/out/src/WalletLogger.js.map +0 -1
- package/out/src/WalletPermissionsManager.d.ts +0 -981
- package/out/src/WalletPermissionsManager.d.ts.map +0 -1
- package/out/src/WalletPermissionsManager.js +0 -3744
- package/out/src/WalletPermissionsManager.js.map +0 -1
- package/out/src/WalletSettingsManager.d.ts +0 -53
- package/out/src/WalletSettingsManager.d.ts.map +0 -1
- package/out/src/WalletSettingsManager.js +0 -86
- package/out/src/WalletSettingsManager.js.map +0 -1
- package/out/src/fundWalletP2PKH.d.ts +0 -48
- package/out/src/fundWalletP2PKH.d.ts.map +0 -1
- package/out/src/fundWalletP2PKH.js +0 -239
- package/out/src/fundWalletP2PKH.js.map +0 -1
- package/out/src/index.client.d.ts +0 -23
- package/out/src/index.client.d.ts.map +0 -1
- package/out/src/index.client.js +0 -62
- package/out/src/index.client.js.map +0 -1
- package/out/src/mockchain/MockChainMigrations.d.ts +0 -20
- package/out/src/mockchain/MockChainMigrations.d.ts.map +0 -1
- package/out/src/mockchain/MockChainMigrations.js +0 -67
- package/out/src/mockchain/MockChainMigrations.js.map +0 -1
- package/out/src/mockchain/MockChainStorage.d.ts +0 -58
- package/out/src/mockchain/MockChainStorage.d.ts.map +0 -1
- package/out/src/mockchain/MockChainStorage.js +0 -102
- package/out/src/mockchain/MockChainStorage.js.map +0 -1
- package/out/src/mockchain/MockChainTracker.d.ts +0 -28
- package/out/src/mockchain/MockChainTracker.d.ts.map +0 -1
- package/out/src/mockchain/MockChainTracker.js +0 -97
- package/out/src/mockchain/MockChainTracker.js.map +0 -1
- package/out/src/mockchain/MockMiner.d.ts +0 -16
- package/out/src/mockchain/MockMiner.d.ts.map +0 -1
- package/out/src/mockchain/MockMiner.js +0 -137
- package/out/src/mockchain/MockMiner.js.map +0 -1
- package/out/src/mockchain/MockServices.d.ts +0 -53
- package/out/src/mockchain/MockServices.d.ts.map +0 -1
- package/out/src/mockchain/MockServices.js +0 -459
- package/out/src/mockchain/MockServices.js.map +0 -1
- package/out/src/mockchain/merkleTree.d.ts +0 -13
- package/out/src/mockchain/merkleTree.d.ts.map +0 -1
- package/out/src/mockchain/merkleTree.js +0 -122
- package/out/src/mockchain/merkleTree.js.map +0 -1
- package/out/src/monitor/Monitor.d.ts +0 -189
- package/out/src/monitor/Monitor.d.ts.map +0 -1
- package/out/src/monitor/Monitor.js +0 -386
- package/out/src/monitor/Monitor.js.map +0 -1
- package/out/src/monitor/tasks/TaskArcSSE.d.ts +0 -29
- package/out/src/monitor/tasks/TaskArcSSE.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskArcSSE.js +0 -259
- package/out/src/monitor/tasks/TaskArcSSE.js.map +0 -1
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts +0 -53
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskCheckForProofs.js +0 -224
- package/out/src/monitor/tasks/TaskCheckForProofs.js.map +0 -1
- package/out/src/monitor/tasks/TaskCheckNoSends.d.ts +0 -91
- package/out/src/monitor/tasks/TaskCheckNoSends.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskCheckNoSends.js +0 -159
- package/out/src/monitor/tasks/TaskCheckNoSends.js.map +0 -1
- package/out/src/monitor/tasks/TaskClock.d.ts +0 -14
- package/out/src/monitor/tasks/TaskClock.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskClock.js +0 -29
- package/out/src/monitor/tasks/TaskClock.js.map +0 -1
- package/out/src/monitor/tasks/TaskFailAbandoned.d.ts +0 -20
- package/out/src/monitor/tasks/TaskFailAbandoned.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskFailAbandoned.js +0 -53
- package/out/src/monitor/tasks/TaskFailAbandoned.js.map +0 -1
- package/out/src/monitor/tasks/TaskMineBlock.d.ts +0 -13
- package/out/src/monitor/tasks/TaskMineBlock.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskMineBlock.js +0 -30
- package/out/src/monitor/tasks/TaskMineBlock.js.map +0 -1
- package/out/src/monitor/tasks/TaskMonitorCallHistory.d.ts +0 -12
- package/out/src/monitor/tasks/TaskMonitorCallHistory.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskMonitorCallHistory.js +0 -25
- package/out/src/monitor/tasks/TaskMonitorCallHistory.js.map +0 -1
- package/out/src/monitor/tasks/TaskNewHeader.d.ts +0 -50
- package/out/src/monitor/tasks/TaskNewHeader.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskNewHeader.js +0 -97
- package/out/src/monitor/tasks/TaskNewHeader.js.map +0 -1
- package/out/src/monitor/tasks/TaskPurge.d.ts +0 -45
- package/out/src/monitor/tasks/TaskPurge.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskPurge.js +0 -36
- package/out/src/monitor/tasks/TaskPurge.js.map +0 -1
- package/out/src/monitor/tasks/TaskReorg.d.ts +0 -43
- package/out/src/monitor/tasks/TaskReorg.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskReorg.js +0 -84
- package/out/src/monitor/tasks/TaskReorg.js.map +0 -1
- package/out/src/monitor/tasks/TaskReviewDoubleSpends.d.ts +0 -26
- package/out/src/monitor/tasks/TaskReviewDoubleSpends.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskReviewDoubleSpends.js +0 -127
- package/out/src/monitor/tasks/TaskReviewDoubleSpends.js.map +0 -1
- package/out/src/monitor/tasks/TaskReviewProvenTxs.d.ts +0 -34
- package/out/src/monitor/tasks/TaskReviewProvenTxs.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskReviewProvenTxs.js +0 -136
- package/out/src/monitor/tasks/TaskReviewProvenTxs.js.map +0 -1
- package/out/src/monitor/tasks/TaskReviewStatus.d.ts +0 -26
- package/out/src/monitor/tasks/TaskReviewStatus.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskReviewStatus.js +0 -50
- package/out/src/monitor/tasks/TaskReviewStatus.js.map +0 -1
- package/out/src/monitor/tasks/TaskReviewUtxos.d.ts +0 -23
- package/out/src/monitor/tasks/TaskReviewUtxos.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskReviewUtxos.js +0 -75
- package/out/src/monitor/tasks/TaskReviewUtxos.js.map +0 -1
- package/out/src/monitor/tasks/TaskSendWaiting.d.ts +0 -48
- package/out/src/monitor/tasks/TaskSendWaiting.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskSendWaiting.js +0 -202
- package/out/src/monitor/tasks/TaskSendWaiting.js.map +0 -1
- package/out/src/monitor/tasks/TaskUnFail.d.ts +0 -41
- package/out/src/monitor/tasks/TaskUnFail.d.ts.map +0 -1
- package/out/src/monitor/tasks/TaskUnFail.js +0 -89
- package/out/src/monitor/tasks/TaskUnFail.js.map +0 -1
- package/out/src/monitor/tasks/WalletMonitorTask.d.ts +0 -40
- package/out/src/monitor/tasks/WalletMonitorTask.d.ts.map +0 -1
- package/out/src/monitor/tasks/WalletMonitorTask.js +0 -42
- package/out/src/monitor/tasks/WalletMonitorTask.js.map +0 -1
- package/out/src/sdk/CertOpsWallet.d.ts +0 -7
- package/out/src/sdk/CertOpsWallet.d.ts.map +0 -1
- package/out/src/sdk/CertOpsWallet.js +0 -3
- package/out/src/sdk/CertOpsWallet.js.map +0 -1
- package/out/src/sdk/PrivilegedKeyManager.d.ts +0 -125
- package/out/src/sdk/PrivilegedKeyManager.d.ts.map +0 -1
- package/out/src/sdk/PrivilegedKeyManager.js +0 -320
- package/out/src/sdk/PrivilegedKeyManager.js.map +0 -1
- package/out/src/sdk/WERR_errors.d.ts +0 -136
- package/out/src/sdk/WERR_errors.d.ts.map +0 -1
- package/out/src/sdk/WERR_errors.js +0 -233
- package/out/src/sdk/WERR_errors.js.map +0 -1
- package/out/src/sdk/WalletError.d.ts +0 -63
- package/out/src/sdk/WalletError.d.ts.map +0 -1
- package/out/src/sdk/WalletError.js +0 -177
- package/out/src/sdk/WalletError.js.map +0 -1
- package/out/src/sdk/WalletErrorFromJson.d.ts +0 -12
- package/out/src/sdk/WalletErrorFromJson.d.ts.map +0 -1
- package/out/src/sdk/WalletErrorFromJson.js +0 -67
- package/out/src/sdk/WalletErrorFromJson.js.map +0 -1
- package/out/src/sdk/WalletServices.interfaces.d.ts +0 -643
- package/out/src/sdk/WalletServices.interfaces.d.ts.map +0 -1
- package/out/src/sdk/WalletServices.interfaces.js +0 -3
- package/out/src/sdk/WalletServices.interfaces.js.map +0 -1
- package/out/src/sdk/WalletSigner.interfaces.d.ts +0 -10
- package/out/src/sdk/WalletSigner.interfaces.d.ts.map +0 -1
- package/out/src/sdk/WalletSigner.interfaces.js +0 -3
- package/out/src/sdk/WalletSigner.interfaces.js.map +0 -1
- package/out/src/sdk/WalletStorage.interfaces.d.ts +0 -539
- package/out/src/sdk/WalletStorage.interfaces.d.ts.map +0 -1
- package/out/src/sdk/WalletStorage.interfaces.js +0 -3
- package/out/src/sdk/WalletStorage.interfaces.js.map +0 -1
- package/out/src/sdk/index.d.ts +0 -11
- package/out/src/sdk/index.d.ts.map +0 -1
- package/out/src/sdk/index.js +0 -29
- package/out/src/sdk/index.js.map +0 -1
- package/out/src/sdk/types.d.ts +0 -178
- package/out/src/sdk/types.d.ts.map +0 -1
- package/out/src/sdk/types.js +0 -104
- package/out/src/sdk/types.js.map +0 -1
- package/out/src/services/ServiceCollection.d.ts +0 -79
- package/out/src/services/ServiceCollection.d.ts.map +0 -1
- package/out/src/services/ServiceCollection.js +0 -197
- package/out/src/services/ServiceCollection.js.map +0 -1
- package/out/src/services/Services.d.ts +0 -97
- package/out/src/services/Services.d.ts.map +0 -1
- package/out/src/services/Services.js +0 -729
- package/out/src/services/Services.js.map +0 -1
- package/out/src/services/chaintracker/BHServiceClient.d.ts +0 -39
- package/out/src/services/chaintracker/BHServiceClient.d.ts.map +0 -1
- package/out/src/services/chaintracker/BHServiceClient.js +0 -180
- package/out/src/services/chaintracker/BHServiceClient.js.map +0 -1
- package/out/src/services/chaintracker/ChaintracksChainTracker.d.ts +0 -16
- package/out/src/services/chaintracker/ChaintracksChainTracker.d.ts.map +0 -1
- package/out/src/services/chaintracker/ChaintracksChainTracker.js +0 -59
- package/out/src/services/chaintracker/ChaintracksChainTracker.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.d.ts +0 -57
- package/out/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.js +0 -38
- package/out/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.d.ts +0 -70
- package/out/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BulkStorageApi.d.ts +0 -81
- package/out/src/services/chaintracker/chaintracks/Api/BulkStorageApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/BulkStorageApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/BulkStorageApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksApi.d.ts +0 -54
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.d.ts +0 -163
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.d.ts +0 -18
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.d.ts +0 -55
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.d.ts +0 -338
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.d.ts +0 -19
- package/out/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.js +0 -3
- package/out/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts +0 -113
- package/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Chaintracks.js +0 -607
- package/out/src/services/chaintracker/chaintracks/Chaintracks.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.d.ts +0 -38
- package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.js +0 -149
- package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.d.ts +0 -52
- package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js +0 -232
- package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.d.ts +0 -60
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.js +0 -126
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.d.ts +0 -75
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.js +0 -121
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.d.ts +0 -13
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.js +0 -20
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.d.ts +0 -62
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.js +0 -61
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.d.ts +0 -62
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.js +0 -44
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.d.ts +0 -24
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.js +0 -55
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.d.ts +0 -64
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.js +0 -95
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.d.ts +0 -171
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js +0 -487
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.d.ts +0 -89
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.js +0 -142
- package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.d.ts +0 -23
- package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.js +0 -63
- package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts +0 -68
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js +0 -311
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.d.ts +0 -92
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.js +0 -467
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.d.ts +0 -37
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js +0 -261
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/configureChaintracksIngestors.d.ts +0 -22
- package/out/src/services/chaintracker/chaintracks/configureChaintracksIngestors.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/configureChaintracksIngestors.js +0 -54
- package/out/src/services/chaintracker/chaintracks/configureChaintracksIngestors.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.d.ts +0 -5
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.js +0 -29
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.d.ts +0 -5
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.js +0 -29
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.d.ts +0 -13
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.js +0 -27
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.d.ts +0 -13
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.js +0 -27
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/index.client.d.ts +0 -4
- package/out/src/services/chaintracker/chaintracks/index.client.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/index.client.js +0 -20
- package/out/src/services/chaintracker/chaintracks/index.client.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/index.mobile.d.ts +0 -32
- package/out/src/services/chaintracker/chaintracks/index.mobile.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/index.mobile.js +0 -70
- package/out/src/services/chaintracker/chaintracks/index.mobile.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.d.ts +0 -142
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.js +0 -821
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataReader.d.ts +0 -22
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataReader.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataReader.js +0 -65
- package/out/src/services/chaintracker/chaintracks/util/BulkFileDataReader.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts +0 -78
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js +0 -284
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkHeaderFile.d.ts +0 -129
- package/out/src/services/chaintracker/chaintracks/util/BulkHeaderFile.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkHeaderFile.js +0 -168
- package/out/src/services/chaintracker/chaintracks/util/BulkHeaderFile.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.d.ts +0 -21
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js +0 -108
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/HeightRange.d.ts +0 -90
- package/out/src/services/chaintracker/chaintracks/util/HeightRange.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/HeightRange.js +0 -150
- package/out/src/services/chaintracker/chaintracks/util/HeightRange.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.d.ts +0 -14
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.js +0 -86
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts +0 -159
- package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js +0 -530
- package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/dirtyHashes.d.ts +0 -20
- package/out/src/services/chaintracker/chaintracks/util/dirtyHashes.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/dirtyHashes.js +0 -31
- package/out/src/services/chaintracker/chaintracks/util/dirtyHashes.js.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.d.ts +0 -37
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.d.ts.map +0 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js +0 -429
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js.map +0 -1
- package/out/src/services/chaintracker/index.client.d.ts +0 -4
- package/out/src/services/chaintracker/index.client.d.ts.map +0 -1
- package/out/src/services/chaintracker/index.client.js +0 -20
- package/out/src/services/chaintracker/index.client.js.map +0 -1
- package/out/src/services/createDefaultWalletServicesOptions.d.ts +0 -25
- package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +0 -1
- package/out/src/services/createDefaultWalletServicesOptions.js +0 -143
- package/out/src/services/createDefaultWalletServicesOptions.js.map +0 -1
- package/out/src/services/networkConfig.d.ts +0 -29
- package/out/src/services/networkConfig.d.ts.map +0 -1
- package/out/src/services/networkConfig.js +0 -54
- package/out/src/services/networkConfig.js.map +0 -1
- package/out/src/services/providers/ARC.d.ts +0 -96
- package/out/src/services/providers/ARC.d.ts.map +0 -1
- package/out/src/services/providers/ARC.js +0 -332
- package/out/src/services/providers/ARC.js.map +0 -1
- package/out/src/services/providers/ArcSSEClient.d.ts +0 -58
- package/out/src/services/providers/ArcSSEClient.d.ts.map +0 -1
- package/out/src/services/providers/ArcSSEClient.js +0 -115
- package/out/src/services/providers/ArcSSEClient.js.map +0 -1
- package/out/src/services/providers/Arcade.d.ts +0 -73
- package/out/src/services/providers/Arcade.d.ts.map +0 -1
- package/out/src/services/providers/Arcade.js +0 -330
- package/out/src/services/providers/Arcade.js.map +0 -1
- package/out/src/services/providers/Bitails.d.ts +0 -50
- package/out/src/services/providers/Bitails.d.ts.map +0 -1
- package/out/src/services/providers/Bitails.js +0 -237
- package/out/src/services/providers/Bitails.js.map +0 -1
- package/out/src/services/providers/SdkWhatsOnChain.d.ts +0 -21
- package/out/src/services/providers/SdkWhatsOnChain.d.ts.map +0 -1
- package/out/src/services/providers/SdkWhatsOnChain.js +0 -82
- package/out/src/services/providers/SdkWhatsOnChain.js.map +0 -1
- package/out/src/services/providers/WhatsOnChain.d.ts +0 -132
- package/out/src/services/providers/WhatsOnChain.d.ts.map +0 -1
- package/out/src/services/providers/WhatsOnChain.js +0 -558
- package/out/src/services/providers/WhatsOnChain.js.map +0 -1
- package/out/src/services/providers/exchangeRates.d.ts +0 -12
- package/out/src/services/providers/exchangeRates.d.ts.map +0 -1
- package/out/src/services/providers/exchangeRates.js +0 -244
- package/out/src/services/providers/exchangeRates.js.map +0 -1
- package/out/src/services/providers/getBeefForTxid.d.ts +0 -4
- package/out/src/services/providers/getBeefForTxid.d.ts.map +0 -1
- package/out/src/services/providers/getBeefForTxid.js +0 -287
- package/out/src/services/providers/getBeefForTxid.js.map +0 -1
- package/out/src/services/providers/whatsOnChainHelpers.d.ts +0 -68
- package/out/src/services/providers/whatsOnChainHelpers.d.ts.map +0 -1
- package/out/src/services/providers/whatsOnChainHelpers.js +0 -147
- package/out/src/services/providers/whatsOnChainHelpers.js.map +0 -1
- package/out/src/signer/WalletSigner.d.ts +0 -11
- package/out/src/signer/WalletSigner.d.ts.map +0 -1
- package/out/src/signer/WalletSigner.js +0 -16
- package/out/src/signer/WalletSigner.js.map +0 -1
- package/out/src/signer/methods/acquireDirectCertificate.d.ts +0 -5
- package/out/src/signer/methods/acquireDirectCertificate.d.ts.map +0 -1
- package/out/src/signer/methods/acquireDirectCertificate.js +0 -44
- package/out/src/signer/methods/acquireDirectCertificate.js.map +0 -1
- package/out/src/signer/methods/buildSignableTransaction.d.ts +0 -69
- package/out/src/signer/methods/buildSignableTransaction.d.ts.map +0 -1
- package/out/src/signer/methods/buildSignableTransaction.js +0 -267
- package/out/src/signer/methods/buildSignableTransaction.js.map +0 -1
- package/out/src/signer/methods/completeSignedTransaction.d.ts +0 -10
- package/out/src/signer/methods/completeSignedTransaction.d.ts.map +0 -1
- package/out/src/signer/methods/completeSignedTransaction.js +0 -106
- package/out/src/signer/methods/completeSignedTransaction.js.map +0 -1
- package/out/src/signer/methods/createAction.d.ts +0 -14
- package/out/src/signer/methods/createAction.d.ts.map +0 -1
- package/out/src/signer/methods/createAction.js +0 -123
- package/out/src/signer/methods/createAction.js.map +0 -1
- package/out/src/signer/methods/internalizeAction.d.ts +0 -43
- package/out/src/signer/methods/internalizeAction.d.ts.map +0 -1
- package/out/src/signer/methods/internalizeAction.js +0 -109
- package/out/src/signer/methods/internalizeAction.js.map +0 -1
- package/out/src/signer/methods/proveCertificate.d.ts +0 -5
- package/out/src/signer/methods/proveCertificate.d.ts.map +0 -1
- package/out/src/signer/methods/proveCertificate.js +0 -29
- package/out/src/signer/methods/proveCertificate.js.map +0 -1
- package/out/src/signer/methods/resultBeef.d.ts +0 -4
- package/out/src/signer/methods/resultBeef.d.ts.map +0 -1
- package/out/src/signer/methods/resultBeef.js +0 -12
- package/out/src/signer/methods/resultBeef.js.map +0 -1
- package/out/src/signer/methods/signAction.d.ts +0 -11
- package/out/src/signer/methods/signAction.d.ts.map +0 -1
- package/out/src/signer/methods/signAction.js +0 -43
- package/out/src/signer/methods/signAction.js.map +0 -1
- package/out/src/storage/StorageIdb.d.ts +0 -234
- package/out/src/storage/StorageIdb.d.ts.map +0 -1
- package/out/src/storage/StorageIdb.js +0 -1701
- package/out/src/storage/StorageIdb.js.map +0 -1
- package/out/src/storage/StorageProvider.d.ts +0 -281
- package/out/src/storage/StorageProvider.d.ts.map +0 -1
- package/out/src/storage/StorageProvider.js +0 -924
- package/out/src/storage/StorageProvider.js.map +0 -1
- package/out/src/storage/StorageReader.d.ts +0 -77
- package/out/src/storage/StorageReader.d.ts.map +0 -1
- package/out/src/storage/StorageReader.js +0 -166
- package/out/src/storage/StorageReader.js.map +0 -1
- package/out/src/storage/StorageReaderWriter.d.ts +0 -102
- package/out/src/storage/StorageReaderWriter.d.ts.map +0 -1
- package/out/src/storage/StorageReaderWriter.js +0 -335
- package/out/src/storage/StorageReaderWriter.js.map +0 -1
- package/out/src/storage/StorageSyncReader.d.ts +0 -18
- package/out/src/storage/StorageSyncReader.d.ts.map +0 -1
- package/out/src/storage/StorageSyncReader.js +0 -73
- package/out/src/storage/StorageSyncReader.js.map +0 -1
- package/out/src/storage/WalletStorageManager.d.ts +0 -217
- package/out/src/storage/WalletStorageManager.d.ts.map +0 -1
- package/out/src/storage/WalletStorageManager.js +0 -830
- package/out/src/storage/WalletStorageManager.js.map +0 -1
- package/out/src/storage/idbHelpers.d.ts +0 -42
- package/out/src/storage/idbHelpers.d.ts.map +0 -1
- package/out/src/storage/idbHelpers.js +0 -374
- package/out/src/storage/idbHelpers.js.map +0 -1
- package/out/src/storage/index.client.d.ts +0 -12
- package/out/src/storage/index.client.d.ts.map +0 -1
- package/out/src/storage/index.client.js +0 -28
- package/out/src/storage/index.client.js.map +0 -1
- package/out/src/storage/methods/ListActionsSpecOp.d.ts +0 -23
- package/out/src/storage/methods/ListActionsSpecOp.d.ts.map +0 -1
- package/out/src/storage/methods/ListActionsSpecOp.js +0 -129
- package/out/src/storage/methods/ListActionsSpecOp.js.map +0 -1
- package/out/src/storage/methods/ListOutputsSpecOp.d.ts +0 -42
- package/out/src/storage/methods/ListOutputsSpecOp.d.ts.map +0 -1
- package/out/src/storage/methods/ListOutputsSpecOp.js +0 -154
- package/out/src/storage/methods/ListOutputsSpecOp.js.map +0 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts +0 -124
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +0 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js +0 -490
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +0 -1
- package/out/src/storage/methods/createAction.d.ts +0 -13
- package/out/src/storage/methods/createAction.d.ts.map +0 -1
- package/out/src/storage/methods/createAction.js +0 -793
- package/out/src/storage/methods/createAction.js.map +0 -1
- package/out/src/storage/methods/generateChange.d.ts +0 -121
- package/out/src/storage/methods/generateChange.d.ts.map +0 -1
- package/out/src/storage/methods/generateChange.js +0 -496
- package/out/src/storage/methods/generateChange.js.map +0 -1
- package/out/src/storage/methods/getBeefForTransaction.d.ts +0 -23
- package/out/src/storage/methods/getBeefForTransaction.d.ts.map +0 -1
- package/out/src/storage/methods/getBeefForTransaction.js +0 -132
- package/out/src/storage/methods/getBeefForTransaction.js.map +0 -1
- package/out/src/storage/methods/getSyncChunk.d.ts +0 -10
- package/out/src/storage/methods/getSyncChunk.d.ts.map +0 -1
- package/out/src/storage/methods/getSyncChunk.js +0 -273
- package/out/src/storage/methods/getSyncChunk.js.map +0 -1
- package/out/src/storage/methods/internalizeAction.d.ts +0 -102
- package/out/src/storage/methods/internalizeAction.d.ts.map +0 -1
- package/out/src/storage/methods/internalizeAction.js +0 -670
- package/out/src/storage/methods/internalizeAction.js.map +0 -1
- package/out/src/storage/methods/listActionsIdb.d.ts +0 -5
- package/out/src/storage/methods/listActionsIdb.d.ts.map +0 -1
- package/out/src/storage/methods/listActionsIdb.js +0 -142
- package/out/src/storage/methods/listActionsIdb.js.map +0 -1
- package/out/src/storage/methods/listCertificates.d.ts +0 -5
- package/out/src/storage/methods/listCertificates.d.ts.map +0 -1
- package/out/src/storage/methods/listCertificates.js +0 -68
- package/out/src/storage/methods/listCertificates.js.map +0 -1
- package/out/src/storage/methods/listOutputsIdb.d.ts +0 -5
- package/out/src/storage/methods/listOutputsIdb.d.ts.map +0 -1
- package/out/src/storage/methods/listOutputsIdb.js +0 -195
- package/out/src/storage/methods/listOutputsIdb.js.map +0 -1
- package/out/src/storage/methods/managedChange.d.ts +0 -26
- package/out/src/storage/methods/managedChange.d.ts.map +0 -1
- package/out/src/storage/methods/managedChange.js +0 -43
- package/out/src/storage/methods/managedChange.js.map +0 -1
- package/out/src/storage/methods/offsetKey.d.ts +0 -24
- package/out/src/storage/methods/offsetKey.d.ts.map +0 -1
- package/out/src/storage/methods/offsetKey.js +0 -65
- package/out/src/storage/methods/offsetKey.js.map +0 -1
- package/out/src/storage/methods/processAction.d.ts +0 -43
- package/out/src/storage/methods/processAction.d.ts.map +0 -1
- package/out/src/storage/methods/processAction.js +0 -313
- package/out/src/storage/methods/processAction.js.map +0 -1
- package/out/src/storage/methods/purgeDataIdb.d.ts +0 -4
- package/out/src/storage/methods/purgeDataIdb.d.ts.map +0 -1
- package/out/src/storage/methods/purgeDataIdb.js +0 -9
- package/out/src/storage/methods/purgeDataIdb.js.map +0 -1
- package/out/src/storage/methods/reviewStatusIdb.d.ts +0 -20
- package/out/src/storage/methods/reviewStatusIdb.d.ts.map +0 -1
- package/out/src/storage/methods/reviewStatusIdb.js +0 -76
- package/out/src/storage/methods/reviewStatusIdb.js.map +0 -1
- package/out/src/storage/methods/utils.d.ts +0 -25
- package/out/src/storage/methods/utils.d.ts.map +0 -1
- package/out/src/storage/methods/utils.js +0 -59
- package/out/src/storage/methods/utils.js.map +0 -1
- package/out/src/storage/portable/index.d.ts +0 -55
- package/out/src/storage/portable/index.d.ts.map +0 -1
- package/out/src/storage/portable/index.js +0 -872
- package/out/src/storage/portable/index.js.map +0 -1
- package/out/src/storage/remoting/BinaryJson.d.ts +0 -10
- package/out/src/storage/remoting/BinaryJson.d.ts.map +0 -1
- package/out/src/storage/remoting/BinaryJson.js +0 -172
- package/out/src/storage/remoting/BinaryJson.js.map +0 -1
- package/out/src/storage/remoting/StorageClient.d.ts +0 -26
- package/out/src/storage/remoting/StorageClient.d.ts.map +0 -1
- package/out/src/storage/remoting/StorageClient.js +0 -98
- package/out/src/storage/remoting/StorageClient.js.map +0 -1
- package/out/src/storage/remoting/StorageClientBase.d.ts +0 -299
- package/out/src/storage/remoting/StorageClientBase.d.ts.map +0 -1
- package/out/src/storage/remoting/StorageClientBase.js +0 -381
- package/out/src/storage/remoting/StorageClientBase.js.map +0 -1
- package/out/src/storage/remoting/entityValidationHelpers.d.ts +0 -29
- package/out/src/storage/remoting/entityValidationHelpers.d.ts.map +0 -1
- package/out/src/storage/remoting/entityValidationHelpers.js +0 -91
- package/out/src/storage/remoting/entityValidationHelpers.js.map +0 -1
- package/out/src/storage/schema/StorageIdbSchema.d.ts +0 -133
- package/out/src/storage/schema/StorageIdbSchema.d.ts.map +0 -1
- package/out/src/storage/schema/StorageIdbSchema.js +0 -3
- package/out/src/storage/schema/StorageIdbSchema.js.map +0 -1
- package/out/src/storage/schema/entities/EntityBase.d.ts +0 -106
- package/out/src/storage/schema/entities/EntityBase.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityBase.js +0 -101
- package/out/src/storage/schema/entities/EntityBase.js.map +0 -1
- package/out/src/storage/schema/entities/EntityCertificate.d.ts +0 -44
- package/out/src/storage/schema/entities/EntityCertificate.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityCertificate.js +0 -163
- package/out/src/storage/schema/entities/EntityCertificate.js.map +0 -1
- package/out/src/storage/schema/entities/EntityCertificateField.d.ts +0 -33
- package/out/src/storage/schema/entities/EntityCertificateField.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityCertificateField.js +0 -110
- package/out/src/storage/schema/entities/EntityCertificateField.js.map +0 -1
- package/out/src/storage/schema/entities/EntityCommission.d.ts +0 -38
- package/out/src/storage/schema/entities/EntityCommission.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityCommission.js +0 -125
- package/out/src/storage/schema/entities/EntityCommission.js.map +0 -1
- package/out/src/storage/schema/entities/EntityOutput.d.ts +0 -68
- package/out/src/storage/schema/entities/EntityOutput.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityOutput.js +0 -261
- package/out/src/storage/schema/entities/EntityOutput.js.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputBasket.d.ts +0 -36
- package/out/src/storage/schema/entities/EntityOutputBasket.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputBasket.js +0 -133
- package/out/src/storage/schema/entities/EntityOutputBasket.js.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputTag.d.ts +0 -32
- package/out/src/storage/schema/entities/EntityOutputTag.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputTag.js +0 -104
- package/out/src/storage/schema/entities/EntityOutputTag.js.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputTagMap.d.ts +0 -29
- package/out/src/storage/schema/entities/EntityOutputTagMap.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityOutputTagMap.js +0 -96
- package/out/src/storage/schema/entities/EntityOutputTagMap.js.map +0 -1
- package/out/src/storage/schema/entities/EntityProvenTx.d.ts +0 -87
- package/out/src/storage/schema/entities/EntityProvenTx.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityProvenTx.js +0 -276
- package/out/src/storage/schema/entities/EntityProvenTx.js.map +0 -1
- package/out/src/storage/schema/entities/EntityProvenTxReq.d.ts +0 -148
- package/out/src/storage/schema/entities/EntityProvenTxReq.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityProvenTxReq.js +0 -570
- package/out/src/storage/schema/entities/EntityProvenTxReq.js.map +0 -1
- package/out/src/storage/schema/entities/EntitySyncState.d.ts +0 -69
- package/out/src/storage/schema/entities/EntitySyncState.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntitySyncState.js +0 -338
- package/out/src/storage/schema/entities/EntitySyncState.js.map +0 -1
- package/out/src/storage/schema/entities/EntityTransaction.d.ts +0 -71
- package/out/src/storage/schema/entities/EntityTransaction.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityTransaction.js +0 -265
- package/out/src/storage/schema/entities/EntityTransaction.js.map +0 -1
- package/out/src/storage/schema/entities/EntityTxLabel.d.ts +0 -32
- package/out/src/storage/schema/entities/EntityTxLabel.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityTxLabel.js +0 -104
- package/out/src/storage/schema/entities/EntityTxLabel.js.map +0 -1
- package/out/src/storage/schema/entities/EntityTxLabelMap.d.ts +0 -29
- package/out/src/storage/schema/entities/EntityTxLabelMap.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityTxLabelMap.js +0 -96
- package/out/src/storage/schema/entities/EntityTxLabelMap.js.map +0 -1
- package/out/src/storage/schema/entities/EntityUser.d.ts +0 -30
- package/out/src/storage/schema/entities/EntityUser.d.ts.map +0 -1
- package/out/src/storage/schema/entities/EntityUser.js +0 -99
- package/out/src/storage/schema/entities/EntityUser.js.map +0 -1
- package/out/src/storage/schema/entities/MergeEntity.d.ts +0 -34
- package/out/src/storage/schema/entities/MergeEntity.d.ts.map +0 -1
- package/out/src/storage/schema/entities/MergeEntity.js +0 -67
- package/out/src/storage/schema/entities/MergeEntity.js.map +0 -1
- package/out/src/storage/schema/entities/index.d.ts +0 -17
- package/out/src/storage/schema/entities/index.d.ts.map +0 -1
- package/out/src/storage/schema/entities/index.js +0 -33
- package/out/src/storage/schema/entities/index.js.map +0 -1
- package/out/src/storage/schema/tables/TableAuthSession.d.ts +0 -20
- package/out/src/storage/schema/tables/TableAuthSession.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableAuthSession.js +0 -15
- package/out/src/storage/schema/tables/TableAuthSession.js.map +0 -1
- package/out/src/storage/schema/tables/TableCertificate.d.ts +0 -21
- package/out/src/storage/schema/tables/TableCertificate.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableCertificate.js +0 -3
- package/out/src/storage/schema/tables/TableCertificate.js.map +0 -1
- package/out/src/storage/schema/tables/TableCertificateField.d.ts +0 -12
- package/out/src/storage/schema/tables/TableCertificateField.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableCertificateField.js +0 -3
- package/out/src/storage/schema/tables/TableCertificateField.js.map +0 -1
- package/out/src/storage/schema/tables/TableCommission.d.ts +0 -13
- package/out/src/storage/schema/tables/TableCommission.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableCommission.js +0 -3
- package/out/src/storage/schema/tables/TableCommission.js.map +0 -1
- package/out/src/storage/schema/tables/TableMonitorEvent.d.ts +0 -9
- package/out/src/storage/schema/tables/TableMonitorEvent.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableMonitorEvent.js +0 -3
- package/out/src/storage/schema/tables/TableMonitorEvent.js.map +0 -1
- package/out/src/storage/schema/tables/TableOutput.d.ts +0 -37
- package/out/src/storage/schema/tables/TableOutput.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableOutput.js +0 -31
- package/out/src/storage/schema/tables/TableOutput.js.map +0 -1
- package/out/src/storage/schema/tables/TableOutputBasket.d.ts +0 -12
- package/out/src/storage/schema/tables/TableOutputBasket.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableOutputBasket.js +0 -3
- package/out/src/storage/schema/tables/TableOutputBasket.js.map +0 -1
- package/out/src/storage/schema/tables/TableOutputTag.d.ts +0 -10
- package/out/src/storage/schema/tables/TableOutputTag.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableOutputTag.js +0 -3
- package/out/src/storage/schema/tables/TableOutputTag.js.map +0 -1
- package/out/src/storage/schema/tables/TableOutputTagMap.d.ts +0 -9
- package/out/src/storage/schema/tables/TableOutputTagMap.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableOutputTagMap.js +0 -3
- package/out/src/storage/schema/tables/TableOutputTagMap.js.map +0 -1
- package/out/src/storage/schema/tables/TableProvenTx.d.ts +0 -14
- package/out/src/storage/schema/tables/TableProvenTx.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableProvenTx.js +0 -3
- package/out/src/storage/schema/tables/TableProvenTx.js.map +0 -1
- package/out/src/storage/schema/tables/TableProvenTxReq.d.ts +0 -87
- package/out/src/storage/schema/tables/TableProvenTxReq.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableProvenTxReq.js +0 -3
- package/out/src/storage/schema/tables/TableProvenTxReq.js.map +0 -1
- package/out/src/storage/schema/tables/TableSettings.d.ts +0 -17
- package/out/src/storage/schema/tables/TableSettings.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableSettings.js +0 -3
- package/out/src/storage/schema/tables/TableSettings.js.map +0 -1
- package/out/src/storage/schema/tables/TableSyncState.d.ts +0 -18
- package/out/src/storage/schema/tables/TableSyncState.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableSyncState.js +0 -3
- package/out/src/storage/schema/tables/TableSyncState.js.map +0 -1
- package/out/src/storage/schema/tables/TableTransaction.d.ts +0 -37
- package/out/src/storage/schema/tables/TableTransaction.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableTransaction.js +0 -21
- package/out/src/storage/schema/tables/TableTransaction.js.map +0 -1
- package/out/src/storage/schema/tables/TableTxLabel.d.ts +0 -10
- package/out/src/storage/schema/tables/TableTxLabel.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableTxLabel.js +0 -3
- package/out/src/storage/schema/tables/TableTxLabel.js.map +0 -1
- package/out/src/storage/schema/tables/TableTxLabelMap.d.ts +0 -9
- package/out/src/storage/schema/tables/TableTxLabelMap.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableTxLabelMap.js +0 -3
- package/out/src/storage/schema/tables/TableTxLabelMap.js.map +0 -1
- package/out/src/storage/schema/tables/TableUser.d.ts +0 -16
- package/out/src/storage/schema/tables/TableUser.d.ts.map +0 -1
- package/out/src/storage/schema/tables/TableUser.js +0 -3
- package/out/src/storage/schema/tables/TableUser.js.map +0 -1
- package/out/src/storage/schema/tables/index.d.ts +0 -18
- package/out/src/storage/schema/tables/index.d.ts.map +0 -1
- package/out/src/storage/schema/tables/index.js +0 -34
- package/out/src/storage/schema/tables/index.js.map +0 -1
- package/out/src/storage/storageProviderHelpers.d.ts +0 -35
- package/out/src/storage/storageProviderHelpers.d.ts.map +0 -1
- package/out/src/storage/storageProviderHelpers.js +0 -100
- package/out/src/storage/storageProviderHelpers.js.map +0 -1
- package/out/src/utility/ScriptTemplateBRC29.d.ts +0 -25
- package/out/src/utility/ScriptTemplateBRC29.d.ts.map +0 -1
- package/out/src/utility/ScriptTemplateBRC29.js +0 -51
- package/out/src/utility/ScriptTemplateBRC29.js.map +0 -1
- package/out/src/utility/aggregateResults.d.ts +0 -8
- package/out/src/utility/aggregateResults.d.ts.map +0 -1
- package/out/src/utility/aggregateResults.js +0 -59
- package/out/src/utility/aggregateResults.js.map +0 -1
- package/out/src/utility/brc114ActionTimeLabels.d.ts +0 -9
- package/out/src/utility/brc114ActionTimeLabels.d.ts.map +0 -1
- package/out/src/utility/brc114ActionTimeLabels.js +0 -58
- package/out/src/utility/brc114ActionTimeLabels.js.map +0 -1
- package/out/src/utility/identityUtils.d.ts +0 -37
- package/out/src/utility/identityUtils.d.ts.map +0 -1
- package/out/src/utility/identityUtils.js +0 -162
- package/out/src/utility/identityUtils.js.map +0 -1
- package/out/src/utility/index.client.d.ts +0 -8
- package/out/src/utility/index.client.d.ts.map +0 -1
- package/out/src/utility/index.client.js +0 -24
- package/out/src/utility/index.client.js.map +0 -1
- package/out/src/utility/parseTxScriptOffsets.d.ts +0 -14
- package/out/src/utility/parseTxScriptOffsets.d.ts.map +0 -1
- package/out/src/utility/parseTxScriptOffsets.js +0 -26
- package/out/src/utility/parseTxScriptOffsets.js.map +0 -1
- package/out/src/utility/stampLog.d.ts +0 -18
- package/out/src/utility/stampLog.d.ts.map +0 -1
- package/out/src/utility/stampLog.js +0 -74
- package/out/src/utility/stampLog.js.map +0 -1
- package/out/src/utility/tscProofToMerklePath.d.ts +0 -8
- package/out/src/utility/tscProofToMerklePath.d.ts.map +0 -1
- package/out/src/utility/tscProofToMerklePath.js +0 -41
- package/out/src/utility/tscProofToMerklePath.js.map +0 -1
- package/out/src/utility/utilityHelpers.d.ts +0 -142
- package/out/src/utility/utilityHelpers.d.ts.map +0 -1
- package/out/src/utility/utilityHelpers.js +0 -319
- package/out/src/utility/utilityHelpers.js.map +0 -1
- package/out/src/utility/utilityHelpers.noBuffer.d.ts +0 -30
- package/out/src/utility/utilityHelpers.noBuffer.d.ts.map +0 -1
- package/out/src/utility/utilityHelpers.noBuffer.js +0 -64
- package/out/src/utility/utilityHelpers.noBuffer.js.map +0 -1
- package/out/src/wab-client/WABClient.d.ts +0 -114
- package/out/src/wab-client/WABClient.d.ts.map +0 -1
- package/out/src/wab-client/WABClient.js +0 -188
- package/out/src/wab-client/WABClient.js.map +0 -1
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts +0 -41
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts.map +0 -1
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js +0 -49
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js.map +0 -1
- package/out/src/wab-client/auth-method-interactors/DevConsoleInteractor.d.ts +0 -11
- package/out/src/wab-client/auth-method-interactors/DevConsoleInteractor.d.ts.map +0 -1
- package/out/src/wab-client/auth-method-interactors/DevConsoleInteractor.js +0 -15
- package/out/src/wab-client/auth-method-interactors/DevConsoleInteractor.js.map +0 -1
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts +0 -5
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts.map +0 -1
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js +0 -9
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js.map +0 -1
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts +0 -10
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts.map +0 -1
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js +0 -14
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js.map +0 -1
- package/out/tsconfig.client.tsbuildinfo +0 -1
|
@@ -1,3744 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WalletPermissionsManager = void 0;
|
|
4
|
-
const sdk_1 = require("@bsv/sdk");
|
|
5
|
-
const brc114ActionTimeLabels_1 = require("./utility/brc114ActionTimeLabels");
|
|
6
|
-
/// /// TODO: ADD SUPPORT FOR ADMIN COUNTERPARTIES BASED ON WALLET STORAGE
|
|
7
|
-
/// /// PROHIBITION OF SPECIAL OPERATIONS IS ALSO CRITICAL.
|
|
8
|
-
/// /// !!!!!!!! SECURITY-CRITICAL ADDITION — DO NOT USE UNTIL IMPLEMENTED.
|
|
9
|
-
function deepEqual(object1, object2) {
|
|
10
|
-
if (object1 === null || object1 === undefined || object2 === null || object2 === undefined) {
|
|
11
|
-
return object1 === object2;
|
|
12
|
-
}
|
|
13
|
-
const keys1 = Object.keys(object1);
|
|
14
|
-
const keys2 = Object.keys(object2);
|
|
15
|
-
if (keys1.length !== keys2.length) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
for (const key of keys1) {
|
|
19
|
-
const val1 = object1[key];
|
|
20
|
-
const val2 = object2[key];
|
|
21
|
-
const areObjects = isObject(val1) && isObject(val2);
|
|
22
|
-
if ((areObjects && !deepEqual(val1, val2)) || (!areObjects && val1 !== val2)) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
function isObject(object) {
|
|
29
|
-
return object != null && typeof object === 'object';
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* A map from each permission type to a special "admin basket" name used for storing
|
|
33
|
-
* the tokens. The tokens themselves are unspent transaction outputs (UTXOs) with a
|
|
34
|
-
* specialized PushDrop script that references the originator, expiry, etc.
|
|
35
|
-
*/
|
|
36
|
-
const BASKET_MAP = {
|
|
37
|
-
protocol: 'admin protocol-permission',
|
|
38
|
-
basket: 'admin basket-access',
|
|
39
|
-
certificate: 'admin certificate-access',
|
|
40
|
-
spending: 'admin spending-authorization'
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* @class WalletPermissionsManager
|
|
44
|
-
*
|
|
45
|
-
* Wraps an underlying BRC-100 `Wallet` implementation with permissions management capabilities.
|
|
46
|
-
* The manager intercepts calls from external applications (identified by originators), checks if the request is allowed,
|
|
47
|
-
* and if not, orchestrates user permission flows. It creates or renews on-chain tokens in special
|
|
48
|
-
* admin baskets to track these authorizations. Finally, it proxies the actual call to the underlying wallet.
|
|
49
|
-
*
|
|
50
|
-
* ### Key Responsibilities:
|
|
51
|
-
* - **Permission Checking**: Before standard wallet operations (e.g. `encrypt`),
|
|
52
|
-
* the manager checks if a valid permission token exists. If not, it attempts to request permission from the user.
|
|
53
|
-
* - **On-Chain Tokens**: When permission is granted, the manager stores it as an unspent "PushDrop" output.
|
|
54
|
-
* This can be spent later to revoke or renew the permission.
|
|
55
|
-
* - **Callbacks**: The manager triggers user-defined callbacks on permission requests (to show a UI prompt),
|
|
56
|
-
* on grants/denials, and on internal processes.
|
|
57
|
-
*
|
|
58
|
-
* ### Implementation Notes:
|
|
59
|
-
* - The manager follows the BRC-100 `createAction` + `signAction` pattern for building or spending these tokens.
|
|
60
|
-
* - Token revocation or renewal uses standard BRC-100 flows: we build a transaction that consumes
|
|
61
|
-
* the old token UTXO and outputs a new one (or none, if fully revoked).
|
|
62
|
-
*/
|
|
63
|
-
class WalletPermissionsManager {
|
|
64
|
-
/** A reference to the BRC-100 wallet instance. */
|
|
65
|
-
underlying;
|
|
66
|
-
/** The "admin" domain or FQDN that is implicitly allowed to do everything. */
|
|
67
|
-
adminOriginator;
|
|
68
|
-
/**
|
|
69
|
-
* Event callbacks that external code can subscribe to, e.g. to show a UI prompt
|
|
70
|
-
* or log events. Each event can have multiple handlers.
|
|
71
|
-
*/
|
|
72
|
-
callbacks = {
|
|
73
|
-
onProtocolPermissionRequested: [],
|
|
74
|
-
onBasketAccessRequested: [],
|
|
75
|
-
onCertificateAccessRequested: [],
|
|
76
|
-
onSpendingAuthorizationRequested: [],
|
|
77
|
-
onGroupedPermissionRequested: [],
|
|
78
|
-
onCounterpartyPermissionRequested: []
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* We queue parallel requests for the same resource so that only one
|
|
82
|
-
* user prompt is created for a single resource. If multiple calls come
|
|
83
|
-
* in at once for the same "protocol:domain:privileged:counterparty" etc.,
|
|
84
|
-
* they get merged.
|
|
85
|
-
*
|
|
86
|
-
* The key is a string derived from the operation; the value is an object with a reference to the
|
|
87
|
-
* associated request and an array of pending promise resolve/reject pairs, one for each active
|
|
88
|
-
* operation that's waiting on the particular resource described by the key.
|
|
89
|
-
*/
|
|
90
|
-
activeRequests = new Map();
|
|
91
|
-
/** Cache recently confirmed permissions to avoid repeated lookups. */
|
|
92
|
-
permissionCache = new Map();
|
|
93
|
-
recentGrants = new Map();
|
|
94
|
-
/**
|
|
95
|
-
* Token mints currently being written on-chain, keyed by permission cache
|
|
96
|
-
* key. A granted permission is only cached once its token finishes minting
|
|
97
|
-
* (network seconds); ensures arriving in that window await the in-flight
|
|
98
|
-
* mint instead of re-prompting the user for a permission they just granted.
|
|
99
|
-
* Stored promises never reject.
|
|
100
|
-
*/
|
|
101
|
-
mintsInFlight = new Map();
|
|
102
|
-
manifestCache = new Map();
|
|
103
|
-
manifestFetchInProgress = new Map();
|
|
104
|
-
static MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
105
|
-
groupedPermissionFlowTail = new Map();
|
|
106
|
-
pactEstablishedCache = new Map();
|
|
107
|
-
/**
|
|
108
|
-
* Parsed BEEF bundles keyed by the raw BEEF array of a `listOutputs`
|
|
109
|
-
* result, so token scans parse each bundle once instead of once per output.
|
|
110
|
-
* `null` records a bundle that failed to parse. WeakMap-keyed so entries
|
|
111
|
-
* are released with their results.
|
|
112
|
-
*/
|
|
113
|
-
parsedBeefCache = new WeakMap();
|
|
114
|
-
/** Counts token-field decrypts so long scans can periodically yield. */
|
|
115
|
-
tokenFieldDecryptCount = 0;
|
|
116
|
-
/** How long a cached permission remains valid (5 minutes). */
|
|
117
|
-
static CACHE_TTL_MS = 5 * 60 * 1000;
|
|
118
|
-
/** Window during which freshly granted permissions are auto-allowed (except spending). */
|
|
119
|
-
static RECENT_GRANT_COVER_MS = 15 * 1000;
|
|
120
|
-
/** Default ports used when normalizing originator values. */
|
|
121
|
-
static DEFAULT_PORTS = {
|
|
122
|
-
'http:': '80',
|
|
123
|
-
'https:': '443'
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
|
-
* Configuration that determines whether to skip or apply various checks and encryption.
|
|
127
|
-
*/
|
|
128
|
-
config;
|
|
129
|
-
/**
|
|
130
|
-
* Constructs a new Permissions Manager instance.
|
|
131
|
-
*
|
|
132
|
-
* @param underlyingWallet The underlying BRC-100 wallet, where requests are forwarded after permission is granted
|
|
133
|
-
* @param adminOriginator The domain or FQDN that is automatically allowed everything
|
|
134
|
-
* @param config A set of boolean flags controlling how strictly permissions are enforced
|
|
135
|
-
*/
|
|
136
|
-
constructor(underlyingWallet, adminOriginator, config = {}) {
|
|
137
|
-
this.underlying = underlyingWallet;
|
|
138
|
-
this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
|
|
139
|
-
// Default all config options to true unless specified
|
|
140
|
-
this.config = {
|
|
141
|
-
seekProtocolPermissionsForSigning: true,
|
|
142
|
-
seekProtocolPermissionsForEncrypting: true,
|
|
143
|
-
seekProtocolPermissionsForHMAC: true,
|
|
144
|
-
seekPermissionsForKeyLinkageRevelation: true,
|
|
145
|
-
seekPermissionsForPublicKeyRevelation: true,
|
|
146
|
-
seekPermissionsForIdentityKeyRevelation: true,
|
|
147
|
-
seekPermissionsForIdentityResolution: true,
|
|
148
|
-
seekBasketInsertionPermissions: true,
|
|
149
|
-
seekBasketRemovalPermissions: true,
|
|
150
|
-
seekBasketListingPermissions: true,
|
|
151
|
-
seekPermissionWhenApplyingActionLabels: true,
|
|
152
|
-
seekPermissionWhenListingActionsByLabel: true,
|
|
153
|
-
seekCertificateDisclosurePermissions: true,
|
|
154
|
-
seekCertificateAcquisitionPermissions: true,
|
|
155
|
-
seekCertificateRelinquishmentPermissions: true,
|
|
156
|
-
seekCertificateListingPermissions: true,
|
|
157
|
-
encryptWalletMetadata: true,
|
|
158
|
-
seekSpendingPermissions: true,
|
|
159
|
-
seekGroupedPermission: true,
|
|
160
|
-
differentiatePrivilegedOperations: true,
|
|
161
|
-
whitelistedCounterparties: {},
|
|
162
|
-
...config // override with user-specified config
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
/* ---------------------------------------------------------------------
|
|
166
|
-
* HELPER METHODS FOR P-MODULE DELEGATION
|
|
167
|
-
* --------------------------------------------------------------------- */
|
|
168
|
-
/**
|
|
169
|
-
* Delegates a wallet method call to a P-module if the basket or protocol name uses a P-scheme.
|
|
170
|
-
* Handles the full request/response transformation flow.
|
|
171
|
-
*
|
|
172
|
-
* @param basketOrProtocolName - The basket or protocol name to check for p-module delegation
|
|
173
|
-
* @param method - The wallet method name being called
|
|
174
|
-
* @param args - The original args passed to the method
|
|
175
|
-
* @param originator - The originator of the request
|
|
176
|
-
* @param underlyingCall - Callback that executes the underlying wallet method with transformed args
|
|
177
|
-
* @returns The transformed response, or null if not a P-basket/protocol (caller should continue normal flow)
|
|
178
|
-
*/
|
|
179
|
-
async delegateToPModuleIfNeeded(basketOrProtocolName, method, args, originator, underlyingCall) {
|
|
180
|
-
// Check if this is a P-protocol/basket
|
|
181
|
-
if (!basketOrProtocolName.startsWith('p ')) {
|
|
182
|
-
return null; // If not, caller should continue normal flow
|
|
183
|
-
}
|
|
184
|
-
const schemeID = basketOrProtocolName.split(' ')[1];
|
|
185
|
-
const module = this.config.permissionModules?.[schemeID];
|
|
186
|
-
if (module == null) {
|
|
187
|
-
throw new Error(`Unsupported P-module scheme: p ${schemeID}`);
|
|
188
|
-
}
|
|
189
|
-
// Transform request with module
|
|
190
|
-
const transformedReq = await module.onRequest({
|
|
191
|
-
method,
|
|
192
|
-
args,
|
|
193
|
-
originator
|
|
194
|
-
});
|
|
195
|
-
// Call underlying method with transformed request
|
|
196
|
-
const results = await underlyingCall(transformedReq.args, originator);
|
|
197
|
-
// Transform response with module
|
|
198
|
-
return await module.onResponse(results, {
|
|
199
|
-
method,
|
|
200
|
-
originator
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Adds a permission module for the given schemeID if needed, throwing if unsupported.
|
|
205
|
-
*/
|
|
206
|
-
addPModuleByScheme(schemeID, kind, pModulesByScheme) {
|
|
207
|
-
if (pModulesByScheme.has(schemeID))
|
|
208
|
-
return;
|
|
209
|
-
const module = this.config.permissionModules?.[schemeID];
|
|
210
|
-
if (module == null) {
|
|
211
|
-
throw new Error(`Unsupported P-${kind} scheme: p ${schemeID}`);
|
|
212
|
-
}
|
|
213
|
-
pModulesByScheme.set(schemeID, module);
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Splits labels into P and non-P lists, registering any P-modules encountered.
|
|
217
|
-
*
|
|
218
|
-
* P-labels follow BRC-111 format: `p <moduleId> <payload>`
|
|
219
|
-
* - Must start with "p " (lowercase p + space)
|
|
220
|
-
* - Module ID must be at least 1 character with no spaces
|
|
221
|
-
* - Single space separates module ID from payload
|
|
222
|
-
* - Payload must be at least 1 character
|
|
223
|
-
*
|
|
224
|
-
* @example Valid: "p btms token123", "p invoicing invoice 2026-02-02"
|
|
225
|
-
* @example Invalid: "p btms" (no payload), "p btms " (empty payload), "p data" (empty moduleId)
|
|
226
|
-
*
|
|
227
|
-
* @param labels - Array of label strings to process
|
|
228
|
-
* @param pModulesByScheme - Map to populate with discovered p-modules
|
|
229
|
-
* @returns Array of non-P labels for normal permission checks
|
|
230
|
-
* @throws Error if p-label format is invalid or module is unsupported
|
|
231
|
-
*/
|
|
232
|
-
splitLabelsByPermissionModule(labels, pModulesByScheme) {
|
|
233
|
-
const nonPLabels = [];
|
|
234
|
-
if (labels == null)
|
|
235
|
-
return nonPLabels;
|
|
236
|
-
for (const label of labels) {
|
|
237
|
-
if (label.startsWith('p ')) {
|
|
238
|
-
// Remove "p " prefix to get "moduleId payload"
|
|
239
|
-
const remainder = label.slice(2);
|
|
240
|
-
// Find the space that separates moduleId from payload
|
|
241
|
-
const separatorIndex = remainder.indexOf(' ');
|
|
242
|
-
// Validate: must have a space (separatorIndex > 0) and payload after it
|
|
243
|
-
// separatorIndex <= 0 means no space found or moduleId is empty
|
|
244
|
-
// separatorIndex === remainder.length - 1 means space is last char (no payload)
|
|
245
|
-
if (separatorIndex <= 0 || separatorIndex === remainder.length - 1) {
|
|
246
|
-
throw new Error(`Invalid P-label format: ${label}`);
|
|
247
|
-
}
|
|
248
|
-
// Reject double spaces after moduleId (payload can't start with space)
|
|
249
|
-
if (remainder[separatorIndex + 1] === ' ') {
|
|
250
|
-
throw new Error(`Invalid P-label format: ${label}`);
|
|
251
|
-
}
|
|
252
|
-
// Extract moduleId (substring before first space)
|
|
253
|
-
const schemeID = remainder.slice(0, separatorIndex);
|
|
254
|
-
// Register the module (throws if unsupported)
|
|
255
|
-
this.addPModuleByScheme(schemeID, 'label', pModulesByScheme);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
// Regular label - add to list for normal permission checks
|
|
259
|
-
nonPLabels.push(label);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return nonPLabels;
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Decrypts custom instructions in listOutputs results if encryption is configured.
|
|
266
|
-
*/
|
|
267
|
-
async decryptListOutputsMetadata(results) {
|
|
268
|
-
if (results.outputs) {
|
|
269
|
-
for (const output of results.outputs) {
|
|
270
|
-
if (output.customInstructions) {
|
|
271
|
-
output.customInstructions = await this.maybeDecryptMetadata(output.customInstructions);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return results;
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Decrypts metadata in listActions results if encryption is configured.
|
|
279
|
-
*/
|
|
280
|
-
async decryptListActionsMetadata(results) {
|
|
281
|
-
if (results.actions) {
|
|
282
|
-
for (const action of results.actions) {
|
|
283
|
-
await this.decryptSingleActionMetadata(action);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return results;
|
|
287
|
-
}
|
|
288
|
-
async decryptSingleActionMetadata(action) {
|
|
289
|
-
if (action.description) {
|
|
290
|
-
action.description = await this.maybeDecryptMetadata(action.description);
|
|
291
|
-
}
|
|
292
|
-
if (action.inputs != null) {
|
|
293
|
-
for (const input of action.inputs) {
|
|
294
|
-
if (input.inputDescription) {
|
|
295
|
-
input.inputDescription = await this.maybeDecryptMetadata(input.inputDescription);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (action.outputs != null) {
|
|
300
|
-
for (const output of action.outputs) {
|
|
301
|
-
await this.decryptActionOutputMetadata(output);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
async decryptActionOutputMetadata(output) {
|
|
306
|
-
if (output.outputDescription) {
|
|
307
|
-
output.outputDescription = await this.maybeDecryptMetadata(output.outputDescription);
|
|
308
|
-
}
|
|
309
|
-
if (output.customInstructions) {
|
|
310
|
-
output.customInstructions = await this.maybeDecryptMetadata(output.customInstructions);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
/* ---------------------------------------------------------------------
|
|
314
|
-
* 1) PUBLIC API FOR REGISTERING CALLBACKS (UI PROMPTS, LOGGING, ETC.)
|
|
315
|
-
* --------------------------------------------------------------------- */
|
|
316
|
-
/**
|
|
317
|
-
* Binds a callback function to a named event, such as `onProtocolPermissionRequested`.
|
|
318
|
-
*
|
|
319
|
-
* @param eventName The name of the event to listen to
|
|
320
|
-
* @param handler A function that handles the event
|
|
321
|
-
* @returns A numeric ID you can use to unbind later
|
|
322
|
-
*/
|
|
323
|
-
bindCallback(eventName, handler) {
|
|
324
|
-
const arr = this.callbacks[eventName];
|
|
325
|
-
arr.push(handler);
|
|
326
|
-
return arr.length - 1;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Unbinds a previously registered callback by either its numeric ID (returned by `bindCallback`)
|
|
330
|
-
* or by exact function reference.
|
|
331
|
-
*
|
|
332
|
-
* @param eventName The event name, e.g. "onProtocolPermissionRequested"
|
|
333
|
-
* @param reference Either the numeric ID or the function reference
|
|
334
|
-
* @returns True if successfully unbound, false otherwise
|
|
335
|
-
*/
|
|
336
|
-
unbindCallback(eventName, reference) {
|
|
337
|
-
if (this.callbacks[eventName] == null)
|
|
338
|
-
return false;
|
|
339
|
-
const arr = this.callbacks[eventName];
|
|
340
|
-
if (typeof reference === 'number') {
|
|
341
|
-
if (arr[reference]) {
|
|
342
|
-
arr[reference] = null;
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
const index = arr.indexOf(reference);
|
|
349
|
-
if (index !== -1) {
|
|
350
|
-
arr[index] = null;
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Internally triggers a named event, calling all subscribed listeners.
|
|
358
|
-
* Each callback is awaited in turn (though errors are swallowed so that
|
|
359
|
-
* one failing callback doesn't prevent the others).
|
|
360
|
-
*
|
|
361
|
-
* @param eventName The event name
|
|
362
|
-
* @param param The parameter object passed to all listeners
|
|
363
|
-
*/
|
|
364
|
-
async callEvent(eventName, param) {
|
|
365
|
-
const arr = this.callbacks[eventName] || [];
|
|
366
|
-
for (const cb of arr) {
|
|
367
|
-
if (typeof cb === 'function') {
|
|
368
|
-
try {
|
|
369
|
-
await cb(param);
|
|
370
|
-
}
|
|
371
|
-
catch (_callbackError) {
|
|
372
|
-
// Intentionally swallow errors from user-provided callbacks to prevent
|
|
373
|
-
// a misbehaving callback from disrupting the event dispatch loop.
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/* ---------------------------------------------------------------------
|
|
379
|
-
* 2) PERMISSION (GRANT / DENY) METHODS
|
|
380
|
-
* --------------------------------------------------------------------- */
|
|
381
|
-
/**
|
|
382
|
-
* Grants a previously requested permission.
|
|
383
|
-
* This method:
|
|
384
|
-
* 1) Resolves all pending promise calls waiting on this request
|
|
385
|
-
* 2) Optionally creates or renews an on-chain PushDrop token (unless `ephemeral===true`)
|
|
386
|
-
*
|
|
387
|
-
* @param params requestID to identify which request is granted, plus optional expiry
|
|
388
|
-
* or `ephemeral` usage, etc.
|
|
389
|
-
*/
|
|
390
|
-
async grantPermission(params) {
|
|
391
|
-
// 1) Identify the matching queued requests in `activeRequests`
|
|
392
|
-
const matching = this.activeRequests.get(params.requestID);
|
|
393
|
-
if (matching == null) {
|
|
394
|
-
throw new Error('Request ID not found.');
|
|
395
|
-
}
|
|
396
|
-
// 2) Mark all matching requests as resolved, deleting the entry
|
|
397
|
-
for (const x of matching.pending) {
|
|
398
|
-
x.resolve(true);
|
|
399
|
-
}
|
|
400
|
-
this.activeRequests.delete(params.requestID);
|
|
401
|
-
// 3) If `ephemeral !== true`, we create or renew an on-chain token
|
|
402
|
-
// Only cache non-ephemeral permissions
|
|
403
|
-
// Ephemeral permissions should not be cached as they are one-time authorizations
|
|
404
|
-
if (!params.ephemeral) {
|
|
405
|
-
const request = matching.request;
|
|
406
|
-
const expiry = params.expiry || 0; // default: never expires
|
|
407
|
-
const key = this.buildRequestKey(request);
|
|
408
|
-
// Several first-contact prompts for the same permission can stack up
|
|
409
|
-
// before any token exists (e.g. one per usage type). Granting them all
|
|
410
|
-
// should yield ONE token: if an identical non-renewal grant just minted
|
|
411
|
-
// (or is minting), don't mint a duplicate. Spending authorizations are
|
|
412
|
-
// excluded — their tokens carry amounts, so every grant is honored.
|
|
413
|
-
let skipDuplicateMint = !request.renewal &&
|
|
414
|
-
request.type !== 'spending' &&
|
|
415
|
-
(this.isPermissionCached(key) || this.isRecentlyGranted(key) || this.mintsInFlight.has(key));
|
|
416
|
-
if (skipDuplicateMint) {
|
|
417
|
-
const inFlight = this.mintsInFlight.get(key);
|
|
418
|
-
if (inFlight != null) {
|
|
419
|
-
await inFlight;
|
|
420
|
-
// The awaited mint may have failed; only skip if it landed.
|
|
421
|
-
skipDuplicateMint = this.isPermissionCached(key) || this.isRecentlyGranted(key);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (!skipDuplicateMint) {
|
|
425
|
-
const mint = request.renewal
|
|
426
|
-
? this.renewPermissionOnChain(request.previousToken, request, expiry, params.amount)
|
|
427
|
-
: this.createPermissionOnChain(request, expiry, params.amount);
|
|
428
|
-
// Publish the in-flight mint so concurrent ensures for the same
|
|
429
|
-
// permission wait for it instead of re-prompting (stored promise
|
|
430
|
-
// never rejects; failures still propagate to this caller below).
|
|
431
|
-
this.mintsInFlight.set(key, mint.then(() => { }, () => { }));
|
|
432
|
-
try {
|
|
433
|
-
await mint;
|
|
434
|
-
}
|
|
435
|
-
finally {
|
|
436
|
-
this.mintsInFlight.delete(key);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
this.cachePermission(key, expiry);
|
|
440
|
-
this.markRecentGrant(request);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* Denies a previously requested permission.
|
|
445
|
-
* This method rejects all pending promise calls waiting on that request
|
|
446
|
-
*
|
|
447
|
-
* @param requestID requestID identifying which request to deny
|
|
448
|
-
*/
|
|
449
|
-
async denyPermission(requestID) {
|
|
450
|
-
// 1) Identify the matching requests
|
|
451
|
-
const matching = this.activeRequests.get(requestID);
|
|
452
|
-
if (matching == null) {
|
|
453
|
-
throw new Error('Request ID not found.');
|
|
454
|
-
}
|
|
455
|
-
// 2) Reject all matching requests, deleting the entry
|
|
456
|
-
// Denials carry the canonical machine-readable code so applications can
|
|
457
|
-
// distinguish user denial from other failures (matches denyActiveRequest).
|
|
458
|
-
const err = new Error('Permission denied.');
|
|
459
|
-
err.code = 'ERR_PERMISSION_DENIED';
|
|
460
|
-
for (const x of matching.pending) {
|
|
461
|
-
x.reject(err);
|
|
462
|
-
}
|
|
463
|
-
this.activeRequests.delete(requestID);
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Grants a previously requested grouped permission.
|
|
467
|
-
* @param params.requestID The ID of the request being granted.
|
|
468
|
-
* @param params.granted A subset of the originally requested permissions that the user has granted.
|
|
469
|
-
* @param params.expiry An optional expiry time (in seconds) for the new permission tokens.
|
|
470
|
-
*/
|
|
471
|
-
async grantGroupedPermission(params) {
|
|
472
|
-
const matching = this.activeRequests.get(params.requestID);
|
|
473
|
-
if (matching == null) {
|
|
474
|
-
throw new Error('Request ID not found.');
|
|
475
|
-
}
|
|
476
|
-
try {
|
|
477
|
-
const originalRequest = matching.request;
|
|
478
|
-
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
479
|
-
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
480
|
-
this.validateGrantedPermissionsSubset(params.granted, requestedPermissions);
|
|
481
|
-
const expiry = params.expiry || 0; // default: never expires
|
|
482
|
-
const toCreate = [];
|
|
483
|
-
const toRenew = [];
|
|
484
|
-
if (params.granted.spendingAuthorization != null) {
|
|
485
|
-
toCreate.push({
|
|
486
|
-
request: {
|
|
487
|
-
type: 'spending',
|
|
488
|
-
originator,
|
|
489
|
-
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
490
|
-
reason: params.granted.spendingAuthorization.description
|
|
491
|
-
},
|
|
492
|
-
expiry: 0,
|
|
493
|
-
amount: params.granted.spendingAuthorization.amount
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
const grantedProtocols = params.granted.protocolPermissions || [];
|
|
497
|
-
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
|
|
498
|
-
const counterparty = (p.protocolID?.[0] ?? 0) === 1 ? '' : p.counterparty || 'self';
|
|
499
|
-
const token = await this.findProtocolToken(originator, false, p.protocolID, counterparty, true, originLookupValues);
|
|
500
|
-
return { p, token, counterparty };
|
|
501
|
-
});
|
|
502
|
-
for (const { p, token, counterparty } of protocolTokens) {
|
|
503
|
-
const request = {
|
|
504
|
-
type: 'protocol',
|
|
505
|
-
originator,
|
|
506
|
-
privileged: false,
|
|
507
|
-
protocolID: p.protocolID,
|
|
508
|
-
counterparty,
|
|
509
|
-
reason: p.description
|
|
510
|
-
};
|
|
511
|
-
if (token == null) {
|
|
512
|
-
toCreate.push({ request, expiry });
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
toRenew.push({ oldToken: token, request, expiry });
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
for (const b of params.granted.basketAccess || []) {
|
|
519
|
-
toCreate.push({
|
|
520
|
-
request: { type: 'basket', originator, basket: b.basket, reason: b.description },
|
|
521
|
-
expiry
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
for (const c of params.granted.certificateAccess || []) {
|
|
525
|
-
toCreate.push({
|
|
526
|
-
request: {
|
|
527
|
-
type: 'certificate',
|
|
528
|
-
originator,
|
|
529
|
-
privileged: false,
|
|
530
|
-
certificate: {
|
|
531
|
-
verifier: c.verifierPublicKey,
|
|
532
|
-
certType: c.type,
|
|
533
|
-
fields: c.fields
|
|
534
|
-
},
|
|
535
|
-
reason: c.description
|
|
536
|
-
},
|
|
537
|
-
expiry
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
const created = await this.createPermissionTokensBestEffort(toCreate);
|
|
541
|
-
const renewed = await this.renewPermissionTokensBestEffort(toRenew);
|
|
542
|
-
for (const req of [...created, ...renewed]) {
|
|
543
|
-
this.markRecentGrant(req);
|
|
544
|
-
}
|
|
545
|
-
// Success - resolve all pending promises for this request
|
|
546
|
-
for (const p of matching.pending) {
|
|
547
|
-
p.resolve(true);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
catch (error) {
|
|
551
|
-
// Failure - reject all pending promises so callers don't hang forever
|
|
552
|
-
for (const p of matching.pending) {
|
|
553
|
-
p.reject(error);
|
|
554
|
-
}
|
|
555
|
-
throw error;
|
|
556
|
-
}
|
|
557
|
-
finally {
|
|
558
|
-
// Always clean up the request entry
|
|
559
|
-
this.activeRequests.delete(params.requestID);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Denies a previously requested grouped permission.
|
|
564
|
-
* @param requestID The ID of the request being denied.
|
|
565
|
-
*/
|
|
566
|
-
/**
|
|
567
|
-
* Validates that every entry in `granted` was part of the original `requested` set.
|
|
568
|
-
* Throws an error on the first mismatch.
|
|
569
|
-
*/
|
|
570
|
-
validateGrantedPermissionsSubset(granted, requested) {
|
|
571
|
-
if ((granted.spendingAuthorization != null) && (requested.spendingAuthorization == null)) {
|
|
572
|
-
throw new Error('Granted spending authorization was not part of the original request.');
|
|
573
|
-
}
|
|
574
|
-
if (granted.protocolPermissions?.some(g => (requested.protocolPermissions?.find(r => deepEqual(r, g))) == null)) {
|
|
575
|
-
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
576
|
-
}
|
|
577
|
-
if (granted.basketAccess?.some(g => (requested.basketAccess?.find(r => deepEqual(r, g))) == null)) {
|
|
578
|
-
throw new Error('Granted basket access permissions are not a subset of the original request.');
|
|
579
|
-
}
|
|
580
|
-
if (granted.certificateAccess?.some(g => (requested.certificateAccess?.find(r => deepEqual(r, g))) == null)) {
|
|
581
|
-
throw new Error('Granted certificate access permissions are not a subset of the original request.');
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
denyActiveRequest(requestID) {
|
|
585
|
-
const matching = this.activeRequests.get(requestID);
|
|
586
|
-
if (matching == null) {
|
|
587
|
-
throw new Error('Request ID not found.');
|
|
588
|
-
}
|
|
589
|
-
const err = new Error('The user has denied the request for permission.');
|
|
590
|
-
err.code = 'ERR_PERMISSION_DENIED';
|
|
591
|
-
for (const p of matching.pending) {
|
|
592
|
-
p.reject(err);
|
|
593
|
-
}
|
|
594
|
-
this.activeRequests.delete(requestID);
|
|
595
|
-
}
|
|
596
|
-
async denyGroupedPermission(requestID) {
|
|
597
|
-
this.denyActiveRequest(requestID);
|
|
598
|
-
}
|
|
599
|
-
async dismissGroupedPermission(requestID) {
|
|
600
|
-
const matching = this.activeRequests.get(requestID);
|
|
601
|
-
if (matching == null) {
|
|
602
|
-
throw new Error('Request ID not found.');
|
|
603
|
-
}
|
|
604
|
-
for (const p of matching.pending) {
|
|
605
|
-
p.resolve(true);
|
|
606
|
-
}
|
|
607
|
-
this.activeRequests.delete(requestID);
|
|
608
|
-
}
|
|
609
|
-
async grantCounterpartyPermission(params) {
|
|
610
|
-
const matching = this.activeRequests.get(params.requestID);
|
|
611
|
-
if (matching == null) {
|
|
612
|
-
throw new Error('Request ID not found.');
|
|
613
|
-
}
|
|
614
|
-
const originalRequest = matching.request;
|
|
615
|
-
const { originator, counterparty, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
616
|
-
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
617
|
-
const getProtoName = (p) => {
|
|
618
|
-
if (!p)
|
|
619
|
-
return undefined;
|
|
620
|
-
if (typeof p.protocolName === 'string')
|
|
621
|
-
return p.protocolName;
|
|
622
|
-
if (Array.isArray(p.protocolID) && typeof p.protocolID[1] === 'string')
|
|
623
|
-
return p.protocolID[1];
|
|
624
|
-
return undefined;
|
|
625
|
-
};
|
|
626
|
-
if (params.granted.protocols?.some(g => {
|
|
627
|
-
const gName = getProtoName(g);
|
|
628
|
-
if (!gName)
|
|
629
|
-
return true;
|
|
630
|
-
return !requestedPermissions.protocols.some(r => r.protocolName === gName);
|
|
631
|
-
})) {
|
|
632
|
-
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
633
|
-
}
|
|
634
|
-
const expiry = params.expiry || 0;
|
|
635
|
-
const toCreate = [];
|
|
636
|
-
const toRenew = [];
|
|
637
|
-
const grantedProtocols = params.granted.protocols || [];
|
|
638
|
-
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
|
|
639
|
-
const protocolName = getProtoName(p);
|
|
640
|
-
if (!protocolName) {
|
|
641
|
-
throw new Error('Invalid counterparty permission protocol entry: missing protocolName/protocolID.');
|
|
642
|
-
}
|
|
643
|
-
const protocolID = [2, protocolName];
|
|
644
|
-
const token = await this.findProtocolToken(originator, false, protocolID, counterparty, true, originLookupValues);
|
|
645
|
-
return { p, token, protocolID };
|
|
646
|
-
});
|
|
647
|
-
for (const { p, token, protocolID } of protocolTokens) {
|
|
648
|
-
const request = {
|
|
649
|
-
type: 'protocol',
|
|
650
|
-
originator,
|
|
651
|
-
privileged: false,
|
|
652
|
-
protocolID,
|
|
653
|
-
counterparty,
|
|
654
|
-
reason: p.description
|
|
655
|
-
};
|
|
656
|
-
if (token == null) {
|
|
657
|
-
toCreate.push({ request, expiry });
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
toRenew.push({ oldToken: token, request, expiry });
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
const created = await this.createPermissionTokensBestEffort(toCreate);
|
|
664
|
-
const renewed = await this.renewPermissionTokensBestEffort(toRenew);
|
|
665
|
-
for (const req of [...created, ...renewed]) {
|
|
666
|
-
this.markRecentGrant(req);
|
|
667
|
-
}
|
|
668
|
-
for (const p of matching.pending) {
|
|
669
|
-
p.resolve(true);
|
|
670
|
-
}
|
|
671
|
-
this.activeRequests.delete(params.requestID);
|
|
672
|
-
}
|
|
673
|
-
async denyCounterpartyPermission(requestID) {
|
|
674
|
-
this.denyActiveRequest(requestID);
|
|
675
|
-
}
|
|
676
|
-
/* ---------------------------------------------------------------------
|
|
677
|
-
* 3) THE "ENSURE" METHODS: CHECK IF PERMISSION EXISTS, OTHERWISE PROMPT
|
|
678
|
-
* --------------------------------------------------------------------- */
|
|
679
|
-
/**
|
|
680
|
-
* Ensures the originator has protocol usage permission.
|
|
681
|
-
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
682
|
-
*/
|
|
683
|
-
async ensureProtocolPermission({ originator, privileged, protocolID, counterparty, reason, seekPermission = true, usageType }) {
|
|
684
|
-
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
685
|
-
originator = normalizedOriginator;
|
|
686
|
-
// 1) adminOriginator can do anything
|
|
687
|
-
if (this.isAdminOriginator(originator))
|
|
688
|
-
return true;
|
|
689
|
-
// 2) If security level=0, we consider it “open” usage
|
|
690
|
-
const [level, protoName] = protocolID;
|
|
691
|
-
if (level === 0)
|
|
692
|
-
return true;
|
|
693
|
-
if (level === 1)
|
|
694
|
-
counterparty = '';
|
|
695
|
-
// 3) If protocol is admin-reserved, block
|
|
696
|
-
if (this.isAdminProtocol(protocolID)) {
|
|
697
|
-
throw new Error(`Protocol “${protoName}” is admin-only.`);
|
|
698
|
-
}
|
|
699
|
-
// Allow the configured exceptions.
|
|
700
|
-
if (this.isProtocolUsageTypeExempted(usageType))
|
|
701
|
-
return true;
|
|
702
|
-
if (!this.config.differentiatePrivilegedOperations)
|
|
703
|
-
privileged = false;
|
|
704
|
-
if (!privileged && this.isWhitelistedCounterpartyProtocol(counterparty, protocolID))
|
|
705
|
-
return true;
|
|
706
|
-
const cacheKey = this.buildRequestKey({ type: 'protocol', originator, privileged, protocolID, counterparty });
|
|
707
|
-
if (await this.hasRecentOrPendingGrant(cacheKey))
|
|
708
|
-
return true;
|
|
709
|
-
// 4) Attempt to find a valid token in the internal basket
|
|
710
|
-
const token = await this.findProtocolToken(originator, privileged, protocolID, counterparty, /* includeExpired= */ true, lookupValues);
|
|
711
|
-
if (token == null) {
|
|
712
|
-
// No token found => request a new one if allowed
|
|
713
|
-
if (!seekPermission)
|
|
714
|
-
throw new Error('No protocol permission token found (seekPermission=false).');
|
|
715
|
-
return await this.requestPermissionFlow({
|
|
716
|
-
type: 'protocol', originator, privileged, protocolID, counterparty, usageType, reason, renewal: false
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
if (this.isTokenExpired(token.expiry)) {
|
|
720
|
-
// has a token but expired => request renewal if allowed
|
|
721
|
-
if (!seekPermission) {
|
|
722
|
-
throw new Error('Protocol permission expired and no further user consent allowed (seekPermission=false).');
|
|
723
|
-
}
|
|
724
|
-
return await this.requestPermissionFlow({
|
|
725
|
-
type: 'protocol', originator, privileged, protocolID, counterparty, usageType, reason,
|
|
726
|
-
renewal: true, previousToken: token
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
// valid and unexpired
|
|
730
|
-
this.cachePermission(cacheKey, token.expiry);
|
|
731
|
-
return true;
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Ensures the originator has basket usage permission for the specified basket.
|
|
735
|
-
* If not, triggers a permission request flow.
|
|
736
|
-
*/
|
|
737
|
-
async ensureBasketAccess({ originator, basket, reason, seekPermission = true, usageType }) {
|
|
738
|
-
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
739
|
-
originator = normalizedOriginator;
|
|
740
|
-
if (this.isAdminOriginator(originator))
|
|
741
|
-
return true;
|
|
742
|
-
if (this.isAdminBasket(basket))
|
|
743
|
-
throw new Error(`Basket “${basket}” is admin-only.`);
|
|
744
|
-
if (!this.isBasketUsageRequired(usageType))
|
|
745
|
-
return true;
|
|
746
|
-
const cacheKey = this.buildRequestKey({ type: 'basket', originator, basket });
|
|
747
|
-
if (await this.hasRecentOrPendingGrant(cacheKey))
|
|
748
|
-
return true;
|
|
749
|
-
const token = await this.findBasketToken(originator, basket, true, lookupValues);
|
|
750
|
-
if (token == null) {
|
|
751
|
-
if (!seekPermission)
|
|
752
|
-
throw new Error('No basket permission found, and no user consent allowed (seekPermission=false).');
|
|
753
|
-
return await this.requestPermissionFlow({ type: 'basket', originator, basket, usageType, reason, renewal: false });
|
|
754
|
-
}
|
|
755
|
-
if (this.isTokenExpired(token.expiry)) {
|
|
756
|
-
if (!seekPermission)
|
|
757
|
-
throw new Error('Basket permission expired (seekPermission=false).');
|
|
758
|
-
return await this.requestPermissionFlow({
|
|
759
|
-
type: 'basket', originator, basket, usageType, reason, renewal: true, previousToken: token
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
this.cachePermission(cacheKey, token.expiry);
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
/** Returns false when the basket usageType does NOT need a permission check (config-gated). */
|
|
766
|
-
isBasketUsageRequired(usageType) {
|
|
767
|
-
if (usageType === 'insertion' && !this.config.seekBasketInsertionPermissions)
|
|
768
|
-
return false;
|
|
769
|
-
if (usageType === 'removal' && !this.config.seekBasketRemovalPermissions)
|
|
770
|
-
return false;
|
|
771
|
-
if (usageType === 'listing' && !this.config.seekBasketListingPermissions)
|
|
772
|
-
return false;
|
|
773
|
-
return true;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Ensures the originator has a valid certificate permission.
|
|
777
|
-
* This is relevant when revealing certificate fields in DCAP contexts.
|
|
778
|
-
*/
|
|
779
|
-
async ensureCertificateAccess({ originator, privileged, verifier, certType, fields, reason, seekPermission = true, usageType }) {
|
|
780
|
-
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
781
|
-
originator = normalizedOriginator;
|
|
782
|
-
if (this.isAdminOriginator(originator))
|
|
783
|
-
return true;
|
|
784
|
-
if (usageType === 'disclosure' && !this.config.seekCertificateDisclosurePermissions) {
|
|
785
|
-
return true;
|
|
786
|
-
}
|
|
787
|
-
if (!this.config.differentiatePrivilegedOperations) {
|
|
788
|
-
privileged = false;
|
|
789
|
-
}
|
|
790
|
-
const cacheKey = this.buildRequestKey({
|
|
791
|
-
type: 'certificate',
|
|
792
|
-
originator,
|
|
793
|
-
privileged,
|
|
794
|
-
certificate: { verifier, certType, fields }
|
|
795
|
-
});
|
|
796
|
-
if (await this.hasRecentOrPendingGrant(cacheKey)) {
|
|
797
|
-
return true;
|
|
798
|
-
}
|
|
799
|
-
const token = await this.findCertificateToken(originator, privileged, verifier, certType, fields,
|
|
800
|
-
/* includeExpired= */ true, lookupValues);
|
|
801
|
-
if (token == null) {
|
|
802
|
-
if (!seekPermission) {
|
|
803
|
-
throw new Error('No certificate permission found (seekPermission=false).');
|
|
804
|
-
}
|
|
805
|
-
return await this.requestPermissionFlow({
|
|
806
|
-
type: 'certificate',
|
|
807
|
-
originator,
|
|
808
|
-
privileged,
|
|
809
|
-
certificate: { verifier, certType, fields },
|
|
810
|
-
usageType,
|
|
811
|
-
reason,
|
|
812
|
-
renewal: false
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
if (this.isTokenExpired(token.expiry)) {
|
|
816
|
-
if (!seekPermission) {
|
|
817
|
-
throw new Error('Certificate permission expired (seekPermission=false).');
|
|
818
|
-
}
|
|
819
|
-
return await this.requestPermissionFlow({
|
|
820
|
-
type: 'certificate',
|
|
821
|
-
originator,
|
|
822
|
-
privileged,
|
|
823
|
-
certificate: { verifier, certType, fields },
|
|
824
|
-
usageType,
|
|
825
|
-
reason,
|
|
826
|
-
renewal: true,
|
|
827
|
-
previousToken: token
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
this.cachePermission(cacheKey, token.expiry);
|
|
831
|
-
return true;
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Ensures the originator has spending authorization (DSAP) for a certain satoshi amount.
|
|
835
|
-
* If the existing token limit is insufficient, attempts to renew. If no token, attempts to create one.
|
|
836
|
-
*/
|
|
837
|
-
async ensureSpendingAuthorization({ originator, satoshis, lineItems, reason, seekPermission = true }) {
|
|
838
|
-
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
839
|
-
originator = normalizedOriginator;
|
|
840
|
-
if (this.isAdminOriginator(originator))
|
|
841
|
-
return true;
|
|
842
|
-
if (!this.config.seekSpendingPermissions) {
|
|
843
|
-
// We skip spending permission entirely
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
const cacheKey = this.buildRequestKey({ type: 'spending', originator, spending: { satoshis } });
|
|
847
|
-
// Spending keys are amount-scoped. The recent-grant window this adds sits
|
|
848
|
-
// inside the pre-existing permissionCache window grantPermission already
|
|
849
|
-
// wrote for spending, so accounting exposure is unchanged.
|
|
850
|
-
if (await this.hasRecentOrPendingGrant(cacheKey)) {
|
|
851
|
-
return true;
|
|
852
|
-
}
|
|
853
|
-
const token = await this.findSpendingToken(originator, lookupValues);
|
|
854
|
-
if (token?.authorizedAmount) {
|
|
855
|
-
// Check how much has been spent so far
|
|
856
|
-
const spentSoFar = await this.querySpentSince(token);
|
|
857
|
-
if (spentSoFar + satoshis <= token.authorizedAmount) {
|
|
858
|
-
this.cachePermission(cacheKey, token.expiry);
|
|
859
|
-
return true;
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
// Renew if possible
|
|
863
|
-
if (!seekPermission) {
|
|
864
|
-
throw new Error(`Spending authorization insufficient for ${satoshis}, no user consent (seekPermission=false).`);
|
|
865
|
-
}
|
|
866
|
-
return await this.requestPermissionFlow({
|
|
867
|
-
type: 'spending',
|
|
868
|
-
originator,
|
|
869
|
-
spending: { satoshis, lineItems },
|
|
870
|
-
reason,
|
|
871
|
-
renewal: true,
|
|
872
|
-
previousToken: token
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
// no token
|
|
878
|
-
if (!seekPermission) {
|
|
879
|
-
throw new Error('No spending authorization found, (seekPermission=false).');
|
|
880
|
-
}
|
|
881
|
-
return await this.requestPermissionFlow({
|
|
882
|
-
type: 'spending',
|
|
883
|
-
originator,
|
|
884
|
-
spending: { satoshis, lineItems },
|
|
885
|
-
reason,
|
|
886
|
-
renewal: false
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* Ensures the originator has label usage permission.
|
|
892
|
-
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
893
|
-
*/
|
|
894
|
-
async ensureLabelAccess({ originator, label, reason, seekPermission = true, usageType }) {
|
|
895
|
-
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
896
|
-
originator = normalizedOriginator;
|
|
897
|
-
// 1) adminOriginator can do anything
|
|
898
|
-
if (this.isAdminOriginator(originator))
|
|
899
|
-
return true;
|
|
900
|
-
// 2) If label is admin-reserved, block
|
|
901
|
-
if (this.isAdminLabel(label)) {
|
|
902
|
-
throw new Error(`Label “${label}” is admin-only.`);
|
|
903
|
-
}
|
|
904
|
-
if (usageType === 'apply' && !this.config.seekPermissionWhenApplyingActionLabels) {
|
|
905
|
-
return true;
|
|
906
|
-
}
|
|
907
|
-
if (usageType === 'list' && !this.config.seekPermissionWhenListingActionsByLabel) {
|
|
908
|
-
return true;
|
|
909
|
-
}
|
|
910
|
-
const cacheKey = this.buildRequestKey({
|
|
911
|
-
type: 'protocol',
|
|
912
|
-
originator,
|
|
913
|
-
privileged: false,
|
|
914
|
-
protocolID: [1, `action label ${label}`],
|
|
915
|
-
counterparty: 'self'
|
|
916
|
-
});
|
|
917
|
-
if (this.isPermissionCached(cacheKey)) {
|
|
918
|
-
return true;
|
|
919
|
-
}
|
|
920
|
-
// 3) Let ensureProtocolPermission handle the rest.
|
|
921
|
-
return await this.ensureProtocolPermission({
|
|
922
|
-
originator,
|
|
923
|
-
privileged: false,
|
|
924
|
-
protocolID: [1, `action label ${label}`],
|
|
925
|
-
counterparty: 'self',
|
|
926
|
-
reason,
|
|
927
|
-
seekPermission,
|
|
928
|
-
usageType: 'generic'
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Returns true when the given usageType is configured to skip permission checks.
|
|
933
|
-
*/
|
|
934
|
-
isProtocolUsageTypeExempted(usageType) {
|
|
935
|
-
if (usageType === 'signing' && !this.config.seekProtocolPermissionsForSigning)
|
|
936
|
-
return true;
|
|
937
|
-
if (usageType === 'encrypting' && !this.config.seekProtocolPermissionsForEncrypting)
|
|
938
|
-
return true;
|
|
939
|
-
if (usageType === 'hmac' && !this.config.seekProtocolPermissionsForHMAC)
|
|
940
|
-
return true;
|
|
941
|
-
if (usageType === 'publicKey' && !this.config.seekPermissionsForPublicKeyRevelation)
|
|
942
|
-
return true;
|
|
943
|
-
if (usageType === 'identityKey' && !this.config.seekPermissionsForIdentityKeyRevelation)
|
|
944
|
-
return true;
|
|
945
|
-
if (usageType === 'linkageRevelation' && !this.config.seekPermissionsForKeyLinkageRevelation)
|
|
946
|
-
return true;
|
|
947
|
-
return false;
|
|
948
|
-
}
|
|
949
|
-
isProtocolInCounterpartyPermissions(protocolID, counterpartyPermissions) {
|
|
950
|
-
const [, name] = protocolID;
|
|
951
|
-
return counterpartyPermissions.protocols.some(p => p.protocolName === name);
|
|
952
|
-
}
|
|
953
|
-
validateCounterpartyPermissions(raw) {
|
|
954
|
-
if (!raw || !Array.isArray(raw.protocols) || raw.protocols.length === 0)
|
|
955
|
-
return null;
|
|
956
|
-
const getCI = (obj, key) => {
|
|
957
|
-
if (!obj || typeof obj !== 'object')
|
|
958
|
-
return undefined;
|
|
959
|
-
const lower = key.toLowerCase();
|
|
960
|
-
const found = Object.keys(obj).find(k => k.toLowerCase() === lower);
|
|
961
|
-
return found ? obj[found] : undefined;
|
|
962
|
-
};
|
|
963
|
-
const parseProtocolId = (v) => {
|
|
964
|
-
if (!Array.isArray(v))
|
|
965
|
-
return null;
|
|
966
|
-
if (v[0] !== 2)
|
|
967
|
-
return null;
|
|
968
|
-
if (typeof v[1] !== 'string')
|
|
969
|
-
return null;
|
|
970
|
-
return v[1];
|
|
971
|
-
};
|
|
972
|
-
const validProtocols = raw.protocols
|
|
973
|
-
.map((p) => {
|
|
974
|
-
const protocolName = getCI(p, 'protocolName');
|
|
975
|
-
if (typeof protocolName === 'string') {
|
|
976
|
-
return {
|
|
977
|
-
protocolName,
|
|
978
|
-
protocolID: [2, protocolName],
|
|
979
|
-
description: typeof p?.description === 'string' ? p.description : undefined
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
const protocolIdRaw = getCI(p, 'protocolID') ?? getCI(p, 'protocolId') ?? getCI(p, 'protocolid');
|
|
983
|
-
const parsed = parseProtocolId(protocolIdRaw);
|
|
984
|
-
if (!parsed)
|
|
985
|
-
return null;
|
|
986
|
-
return {
|
|
987
|
-
protocolName: parsed,
|
|
988
|
-
protocolID: [2, parsed],
|
|
989
|
-
description: typeof p?.description === 'string' ? p.description : undefined
|
|
990
|
-
};
|
|
991
|
-
})
|
|
992
|
-
.filter(Boolean);
|
|
993
|
-
if (validProtocols.length === 0)
|
|
994
|
-
return null;
|
|
995
|
-
return {
|
|
996
|
-
description: typeof raw.description === 'string' ? raw.description : undefined,
|
|
997
|
-
protocols: validProtocols
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
async fetchManifestPermissions(originator) {
|
|
1001
|
-
const cached = this.manifestCache.get(originator);
|
|
1002
|
-
if ((cached != null) && Date.now() - cached.fetchedAt < WalletPermissionsManager.MANIFEST_CACHE_TTL_MS) {
|
|
1003
|
-
return {
|
|
1004
|
-
groupPermissions: cached.groupPermissions,
|
|
1005
|
-
counterpartyPermissions: cached.counterpartyPermissions
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
const inProgress = this.manifestFetchInProgress.get(originator);
|
|
1009
|
-
if (inProgress != null) {
|
|
1010
|
-
return await inProgress;
|
|
1011
|
-
}
|
|
1012
|
-
const fetchPromise = (async () => {
|
|
1013
|
-
try {
|
|
1014
|
-
const proto = originator.startsWith('localhost:') ? 'http' : 'https';
|
|
1015
|
-
const response = await fetch(`${proto}://${originator}/manifest.json`);
|
|
1016
|
-
if (response.ok) {
|
|
1017
|
-
const manifest = await response.json();
|
|
1018
|
-
const namespace = manifest?.metanet || manifest?.babbage;
|
|
1019
|
-
const groupPermissions = namespace?.groupPermissions || null;
|
|
1020
|
-
const counterpartyPermissionsDeclared = this.validateCounterpartyPermissions(namespace?.counterpartyPermissions);
|
|
1021
|
-
this.manifestCache.set(originator, {
|
|
1022
|
-
groupPermissions,
|
|
1023
|
-
counterpartyPermissions: counterpartyPermissionsDeclared,
|
|
1024
|
-
fetchedAt: Date.now()
|
|
1025
|
-
});
|
|
1026
|
-
return { groupPermissions, counterpartyPermissions: counterpartyPermissionsDeclared };
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
catch (_manifestFetchError) {
|
|
1030
|
-
// Manifest fetch or parse failed — fall through to return null/null defaults below
|
|
1031
|
-
}
|
|
1032
|
-
const result = { groupPermissions: null, counterpartyPermissions: null };
|
|
1033
|
-
this.manifestCache.set(originator, { ...result, fetchedAt: Date.now() });
|
|
1034
|
-
return result;
|
|
1035
|
-
})();
|
|
1036
|
-
this.manifestFetchInProgress.set(originator, fetchPromise);
|
|
1037
|
-
try {
|
|
1038
|
-
return await fetchPromise;
|
|
1039
|
-
}
|
|
1040
|
-
finally {
|
|
1041
|
-
this.manifestFetchInProgress.delete(originator);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
async fetchManifestGroupPermissions(originator) {
|
|
1045
|
-
const { groupPermissions } = await this.fetchManifestPermissions(originator);
|
|
1046
|
-
return groupPermissions;
|
|
1047
|
-
}
|
|
1048
|
-
async filterAlreadyGrantedPermissions(originator, groupPermissions) {
|
|
1049
|
-
const permissionsToRequest = {
|
|
1050
|
-
description: groupPermissions.description,
|
|
1051
|
-
protocolPermissions: [],
|
|
1052
|
-
basketAccess: [],
|
|
1053
|
-
certificateAccess: []
|
|
1054
|
-
};
|
|
1055
|
-
const [spendingAuthorization, protocolPermissions, basketAccess, certificateAccess] = await Promise.all([
|
|
1056
|
-
(async () => {
|
|
1057
|
-
if (groupPermissions.spendingAuthorization == null)
|
|
1058
|
-
return undefined;
|
|
1059
|
-
// A standing authorization is a grant regardless of how much of the
|
|
1060
|
-
// monthly allowance has been used — exhaustion is handled by renewal
|
|
1061
|
-
// at spend time. Checking remaining headroom here re-requests (and,
|
|
1062
|
-
// if approved, re-mints) spending after any monthly usage.
|
|
1063
|
-
const { normalized, lookupValues } = this.prepareOriginator(originator);
|
|
1064
|
-
const token = await this.findSpendingToken(normalized, lookupValues);
|
|
1065
|
-
return token != null ? undefined : groupPermissions.spendingAuthorization;
|
|
1066
|
-
})(),
|
|
1067
|
-
(async () => {
|
|
1068
|
-
const protocolChecks = await Promise.all((groupPermissions.protocolPermissions || []).map(async (p) => {
|
|
1069
|
-
if (p.counterparty === '')
|
|
1070
|
-
return null;
|
|
1071
|
-
const [level] = p.protocolID || [0];
|
|
1072
|
-
if (level === 2 && (p.counterparty === undefined || p.counterparty === null)) {
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
|
-
const hasPerm = await this.hasProtocolPermission({
|
|
1076
|
-
originator,
|
|
1077
|
-
privileged: false,
|
|
1078
|
-
protocolID: p.protocolID,
|
|
1079
|
-
counterparty: p.counterparty ?? 'self'
|
|
1080
|
-
});
|
|
1081
|
-
return hasPerm ? null : p;
|
|
1082
|
-
}));
|
|
1083
|
-
return protocolChecks.filter(Boolean);
|
|
1084
|
-
})(),
|
|
1085
|
-
(async () => {
|
|
1086
|
-
const basketChecks = await Promise.all((groupPermissions.basketAccess || []).map(async (b) => {
|
|
1087
|
-
const hasAccess = await this.hasBasketAccess({
|
|
1088
|
-
originator,
|
|
1089
|
-
basket: b.basket
|
|
1090
|
-
});
|
|
1091
|
-
return hasAccess ? null : b;
|
|
1092
|
-
}));
|
|
1093
|
-
return basketChecks.filter(Boolean);
|
|
1094
|
-
})(),
|
|
1095
|
-
(async () => {
|
|
1096
|
-
const certChecks = await Promise.all((groupPermissions.certificateAccess || []).map(async (c) => {
|
|
1097
|
-
const hasAccess = await this.hasCertificateAccess({
|
|
1098
|
-
originator,
|
|
1099
|
-
privileged: false,
|
|
1100
|
-
verifier: c.verifierPublicKey,
|
|
1101
|
-
certType: c.type,
|
|
1102
|
-
fields: c.fields
|
|
1103
|
-
});
|
|
1104
|
-
return hasAccess ? null : c;
|
|
1105
|
-
}));
|
|
1106
|
-
return certChecks.filter(Boolean);
|
|
1107
|
-
})()
|
|
1108
|
-
]);
|
|
1109
|
-
if (spendingAuthorization != null) {
|
|
1110
|
-
permissionsToRequest.spendingAuthorization = spendingAuthorization;
|
|
1111
|
-
}
|
|
1112
|
-
permissionsToRequest.protocolPermissions = protocolPermissions;
|
|
1113
|
-
permissionsToRequest.basketAccess = basketAccess;
|
|
1114
|
-
permissionsToRequest.certificateAccess = certificateAccess;
|
|
1115
|
-
return permissionsToRequest;
|
|
1116
|
-
}
|
|
1117
|
-
hasAnyPermissionsToRequest(permissions) {
|
|
1118
|
-
return !!(permissions.spendingAuthorization ||
|
|
1119
|
-
(permissions.protocolPermissions?.length ?? 0) > 0 ||
|
|
1120
|
-
(permissions.basketAccess?.length ?? 0) > 0 ||
|
|
1121
|
-
(permissions.certificateAccess?.length ?? 0) > 0);
|
|
1122
|
-
}
|
|
1123
|
-
hasGroupedPermissionRequestedHandlers() {
|
|
1124
|
-
const handlers = this.callbacks.onGroupedPermissionRequested || [];
|
|
1125
|
-
return handlers.some(h => typeof h === 'function');
|
|
1126
|
-
}
|
|
1127
|
-
hasCounterpartyPermissionRequestedHandlers() {
|
|
1128
|
-
const handlers = this.callbacks.onCounterpartyPermissionRequested || [];
|
|
1129
|
-
return handlers.some(h => typeof h === 'function');
|
|
1130
|
-
}
|
|
1131
|
-
async hasPactEstablished(originator, counterparty) {
|
|
1132
|
-
if (counterparty === 'self' || counterparty === 'anyone') {
|
|
1133
|
-
return true;
|
|
1134
|
-
}
|
|
1135
|
-
const cacheKey = `${originator}:${counterparty}`;
|
|
1136
|
-
if (this.pactEstablishedCache.has(cacheKey)) {
|
|
1137
|
-
return true;
|
|
1138
|
-
}
|
|
1139
|
-
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
1140
|
-
if (!counterpartyPermissions?.protocols?.length) {
|
|
1141
|
-
return true;
|
|
1142
|
-
}
|
|
1143
|
-
const firstProtocol = counterpartyPermissions.protocols[0];
|
|
1144
|
-
const hasToken = await this.hasProtocolPermission({
|
|
1145
|
-
originator,
|
|
1146
|
-
privileged: false,
|
|
1147
|
-
protocolID: [2, firstProtocol.protocolName],
|
|
1148
|
-
counterparty
|
|
1149
|
-
});
|
|
1150
|
-
if (hasToken) {
|
|
1151
|
-
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
1152
|
-
return true;
|
|
1153
|
-
}
|
|
1154
|
-
return false;
|
|
1155
|
-
}
|
|
1156
|
-
markPactEstablished(originator, counterparty) {
|
|
1157
|
-
const cacheKey = `${originator}:${counterparty}`;
|
|
1158
|
-
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
1159
|
-
}
|
|
1160
|
-
async maybeRequestPact(currentRequest) {
|
|
1161
|
-
if (!this.config.seekGroupedPermission) {
|
|
1162
|
-
return null;
|
|
1163
|
-
}
|
|
1164
|
-
if (!this.hasCounterpartyPermissionRequestedHandlers()) {
|
|
1165
|
-
return null;
|
|
1166
|
-
}
|
|
1167
|
-
if (currentRequest.type !== 'protocol') {
|
|
1168
|
-
return null;
|
|
1169
|
-
}
|
|
1170
|
-
if (currentRequest.privileged) {
|
|
1171
|
-
return null;
|
|
1172
|
-
}
|
|
1173
|
-
const [level] = currentRequest.protocolID;
|
|
1174
|
-
if (level !== 2) {
|
|
1175
|
-
return null;
|
|
1176
|
-
}
|
|
1177
|
-
const originator = currentRequest.originator;
|
|
1178
|
-
const counterparty = currentRequest.counterparty;
|
|
1179
|
-
if (!counterparty || counterparty === 'self' || counterparty === 'anyone') {
|
|
1180
|
-
return null;
|
|
1181
|
-
}
|
|
1182
|
-
if (!/^[0-9a-fA-F]{66}$/.test(counterparty)) {
|
|
1183
|
-
return null;
|
|
1184
|
-
}
|
|
1185
|
-
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
1186
|
-
if (!counterpartyPermissions?.protocols?.length) {
|
|
1187
|
-
return null;
|
|
1188
|
-
}
|
|
1189
|
-
if (!this.isProtocolInCounterpartyPermissions(currentRequest.protocolID, counterpartyPermissions)) {
|
|
1190
|
-
return null;
|
|
1191
|
-
}
|
|
1192
|
-
if (await this.hasPactEstablished(originator, counterparty)) {
|
|
1193
|
-
return null;
|
|
1194
|
-
}
|
|
1195
|
-
const protocolChecks = await Promise.all(counterpartyPermissions.protocols.map(async (p) => {
|
|
1196
|
-
const hasPerm = await this.hasProtocolPermission({
|
|
1197
|
-
originator,
|
|
1198
|
-
privileged: false,
|
|
1199
|
-
protocolID: [2, p.protocolName],
|
|
1200
|
-
counterparty
|
|
1201
|
-
});
|
|
1202
|
-
return hasPerm ? null : p;
|
|
1203
|
-
}));
|
|
1204
|
-
const protocolsToRequest = protocolChecks.filter(Boolean);
|
|
1205
|
-
if (protocolsToRequest.length === 0) {
|
|
1206
|
-
this.markPactEstablished(originator, counterparty);
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
const permissionsToRequest = {
|
|
1210
|
-
description: counterpartyPermissions.description,
|
|
1211
|
-
protocols: protocolsToRequest
|
|
1212
|
-
};
|
|
1213
|
-
const key = `pact:${originator}:${counterparty}`;
|
|
1214
|
-
await this.joinOrCreatePactRequest(key, originator, counterparty, permissionsToRequest, currentRequest.displayOriginator);
|
|
1215
|
-
this.markPactEstablished(originator, counterparty);
|
|
1216
|
-
const satisfied = await this.hasProtocolPermission({
|
|
1217
|
-
originator,
|
|
1218
|
-
privileged: false,
|
|
1219
|
-
protocolID: currentRequest.protocolID,
|
|
1220
|
-
counterparty
|
|
1221
|
-
});
|
|
1222
|
-
return satisfied ? true : null;
|
|
1223
|
-
}
|
|
1224
|
-
async joinOrCreatePactRequest(key, originator, counterparty, permissionsToRequest, displayOriginator) {
|
|
1225
|
-
const existing = this.activeRequests.get(key);
|
|
1226
|
-
if (existing == null) {
|
|
1227
|
-
const counterpartyPromise = new Promise((resolve, reject) => {
|
|
1228
|
-
this.activeRequests.set(key, {
|
|
1229
|
-
request: { originator, counterparty, permissions: permissionsToRequest, displayOriginator },
|
|
1230
|
-
pending: [{ resolve, reject }]
|
|
1231
|
-
});
|
|
1232
|
-
});
|
|
1233
|
-
await this.callEvent('onCounterpartyPermissionRequested', {
|
|
1234
|
-
requestID: key,
|
|
1235
|
-
originator,
|
|
1236
|
-
counterparty,
|
|
1237
|
-
permissions: permissionsToRequest
|
|
1238
|
-
});
|
|
1239
|
-
await counterpartyPromise;
|
|
1240
|
-
}
|
|
1241
|
-
else {
|
|
1242
|
-
const existingRequest = existing.request;
|
|
1243
|
-
for (const p of permissionsToRequest.protocols) {
|
|
1244
|
-
if (!existingRequest.permissions.protocols.some(x => x.protocolName === p.protocolName)) {
|
|
1245
|
-
existingRequest.permissions.protocols.push(p);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
await new Promise((resolve, reject) => {
|
|
1249
|
-
existing.pending.push({ resolve, reject });
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
async maybeRequestPeerGroupedLevel2ProtocolPermissions(currentRequest) {
|
|
1254
|
-
if (!this.config.seekGroupedPermission) {
|
|
1255
|
-
return null;
|
|
1256
|
-
}
|
|
1257
|
-
if (!this.hasGroupedPermissionRequestedHandlers()) {
|
|
1258
|
-
return null;
|
|
1259
|
-
}
|
|
1260
|
-
if (currentRequest.type !== 'protocol') {
|
|
1261
|
-
return null;
|
|
1262
|
-
}
|
|
1263
|
-
const [level] = currentRequest.protocolID;
|
|
1264
|
-
if (level !== 2) {
|
|
1265
|
-
return null;
|
|
1266
|
-
}
|
|
1267
|
-
const originator = currentRequest.originator;
|
|
1268
|
-
const privileged = currentRequest.privileged ?? false;
|
|
1269
|
-
const counterparty = currentRequest.counterparty ?? 'self';
|
|
1270
|
-
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1271
|
-
if (groupPermissions == null) {
|
|
1272
|
-
return null;
|
|
1273
|
-
}
|
|
1274
|
-
const normalizeManifestCounterparty = (cp) => {
|
|
1275
|
-
if (cp === '')
|
|
1276
|
-
return '';
|
|
1277
|
-
if (cp === undefined || cp === null)
|
|
1278
|
-
return counterparty;
|
|
1279
|
-
return cp;
|
|
1280
|
-
};
|
|
1281
|
-
const manifestLevel2ForThisPeer = (groupPermissions.protocolPermissions || [])
|
|
1282
|
-
.filter(p => (p.protocolID?.[0] ?? 0) === 2)
|
|
1283
|
-
.map(p => ({
|
|
1284
|
-
protocolID: p.protocolID,
|
|
1285
|
-
counterparty: normalizeManifestCounterparty(p.counterparty),
|
|
1286
|
-
description: p.description
|
|
1287
|
-
}))
|
|
1288
|
-
.filter(p => p.counterparty !== '')
|
|
1289
|
-
.filter(p => p.counterparty === counterparty);
|
|
1290
|
-
const isCurrentRequestInManifest = manifestLevel2ForThisPeer.some(p => deepEqual(p.protocolID, currentRequest.protocolID));
|
|
1291
|
-
if (!isCurrentRequestInManifest) {
|
|
1292
|
-
return null;
|
|
1293
|
-
}
|
|
1294
|
-
const permissionsToRequest = {
|
|
1295
|
-
protocolPermissions: []
|
|
1296
|
-
};
|
|
1297
|
-
const protocolChecks = await Promise.all(manifestLevel2ForThisPeer.map(async (p) => {
|
|
1298
|
-
const hasPerm = await this.hasProtocolPermission({
|
|
1299
|
-
originator,
|
|
1300
|
-
privileged,
|
|
1301
|
-
protocolID: p.protocolID,
|
|
1302
|
-
counterparty: p.counterparty
|
|
1303
|
-
});
|
|
1304
|
-
return hasPerm
|
|
1305
|
-
? null
|
|
1306
|
-
: {
|
|
1307
|
-
protocolID: p.protocolID,
|
|
1308
|
-
counterparty: p.counterparty,
|
|
1309
|
-
description: p.description
|
|
1310
|
-
};
|
|
1311
|
-
}));
|
|
1312
|
-
permissionsToRequest.protocolPermissions = protocolChecks.filter(Boolean);
|
|
1313
|
-
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1314
|
-
return null;
|
|
1315
|
-
}
|
|
1316
|
-
const key = `group-peer:${originator}:${privileged}:${counterparty}`;
|
|
1317
|
-
await this.joinOrCreateGroupedRequest(key, originator, permissionsToRequest, currentRequest.displayOriginator);
|
|
1318
|
-
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1319
|
-
return satisfied ? true : null;
|
|
1320
|
-
}
|
|
1321
|
-
async withGroupedPermissionFlowLock(originator, fn) {
|
|
1322
|
-
const priorTail = this.groupedPermissionFlowTail.get(originator) || Promise.resolve();
|
|
1323
|
-
const safePriorTail = priorTail.catch(() => { });
|
|
1324
|
-
let release;
|
|
1325
|
-
const gate = new Promise(resolve => {
|
|
1326
|
-
release = resolve;
|
|
1327
|
-
});
|
|
1328
|
-
const currentTail = safePriorTail.then(async () => await gate);
|
|
1329
|
-
this.groupedPermissionFlowTail.set(originator, currentTail);
|
|
1330
|
-
await safePriorTail;
|
|
1331
|
-
try {
|
|
1332
|
-
return await fn();
|
|
1333
|
-
}
|
|
1334
|
-
finally {
|
|
1335
|
-
release?.();
|
|
1336
|
-
currentTail.finally(() => {
|
|
1337
|
-
if (this.groupedPermissionFlowTail.get(originator) === currentTail) {
|
|
1338
|
-
this.groupedPermissionFlowTail.delete(originator);
|
|
1339
|
-
}
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
async checkSpecificPermissionAfterGroupFlow(request) {
|
|
1344
|
-
switch (request.type) {
|
|
1345
|
-
case 'protocol':
|
|
1346
|
-
return await this.hasProtocolPermission({
|
|
1347
|
-
originator: request.originator,
|
|
1348
|
-
privileged: request.privileged ?? false,
|
|
1349
|
-
protocolID: request.protocolID,
|
|
1350
|
-
counterparty: request.counterparty ?? 'self'
|
|
1351
|
-
});
|
|
1352
|
-
case 'basket':
|
|
1353
|
-
return await this.hasBasketAccess({
|
|
1354
|
-
originator: request.originator,
|
|
1355
|
-
basket: request.basket
|
|
1356
|
-
});
|
|
1357
|
-
case 'certificate':
|
|
1358
|
-
return await this.hasCertificateAccess({
|
|
1359
|
-
originator: request.originator,
|
|
1360
|
-
privileged: request.privileged ?? false,
|
|
1361
|
-
verifier: request.certificate.verifier,
|
|
1362
|
-
certType: request.certificate.certType,
|
|
1363
|
-
fields: request.certificate.fields
|
|
1364
|
-
});
|
|
1365
|
-
case 'spending':
|
|
1366
|
-
return await this.hasSpendingAuthorization({
|
|
1367
|
-
originator: request.originator,
|
|
1368
|
-
satoshis: request.spending.satoshis
|
|
1369
|
-
});
|
|
1370
|
-
default:
|
|
1371
|
-
return false;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
isRequestIncludedInGroupPermissions(request, groupPermissions) {
|
|
1375
|
-
switch (request.type) {
|
|
1376
|
-
case 'protocol': {
|
|
1377
|
-
if (request.privileged)
|
|
1378
|
-
return false;
|
|
1379
|
-
const pid = request.protocolID;
|
|
1380
|
-
if (pid == null)
|
|
1381
|
-
return false;
|
|
1382
|
-
const cp = request.counterparty ?? 'self';
|
|
1383
|
-
return !!groupPermissions.protocolPermissions?.some(p => {
|
|
1384
|
-
if (p.counterparty === '')
|
|
1385
|
-
return false;
|
|
1386
|
-
const [level] = p.protocolID || [0];
|
|
1387
|
-
if (level === 2 && (p.counterparty === undefined || p.counterparty === null)) {
|
|
1388
|
-
return false;
|
|
1389
|
-
}
|
|
1390
|
-
const manifestCp = level === 1 ? '' : (p.counterparty ?? 'self');
|
|
1391
|
-
return deepEqual(p.protocolID, pid) && manifestCp === cp;
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
case 'basket': {
|
|
1395
|
-
const basket = request.basket;
|
|
1396
|
-
if (!basket)
|
|
1397
|
-
return false;
|
|
1398
|
-
return !!groupPermissions.basketAccess?.some(b => b.basket === basket);
|
|
1399
|
-
}
|
|
1400
|
-
case 'certificate': {
|
|
1401
|
-
if (request.privileged)
|
|
1402
|
-
return false;
|
|
1403
|
-
const cert = request.certificate;
|
|
1404
|
-
if (cert == null)
|
|
1405
|
-
return false;
|
|
1406
|
-
return !!groupPermissions.certificateAccess?.some(c => {
|
|
1407
|
-
const fieldsA = new Set(c.fields || []);
|
|
1408
|
-
const fieldsB = new Set(cert.fields || []);
|
|
1409
|
-
if (fieldsA.size !== fieldsB.size)
|
|
1410
|
-
return false;
|
|
1411
|
-
for (const f of fieldsA)
|
|
1412
|
-
if (!fieldsB.has(f))
|
|
1413
|
-
return false;
|
|
1414
|
-
return c.type === cert.certType && c.verifierPublicKey === cert.verifier;
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
case 'spending':
|
|
1418
|
-
return groupPermissions.spendingAuthorization != null;
|
|
1419
|
-
default:
|
|
1420
|
-
return false;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
async maybeRequestGroupedPermissions(currentRequest) {
|
|
1424
|
-
if (!this.config.seekGroupedPermission) {
|
|
1425
|
-
return null;
|
|
1426
|
-
}
|
|
1427
|
-
const originator = currentRequest.originator;
|
|
1428
|
-
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1429
|
-
if (groupPermissions == null) {
|
|
1430
|
-
return null;
|
|
1431
|
-
}
|
|
1432
|
-
if (!this.isRequestIncludedInGroupPermissions(currentRequest, groupPermissions)) {
|
|
1433
|
-
return null;
|
|
1434
|
-
}
|
|
1435
|
-
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
|
|
1436
|
-
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1437
|
-
return null;
|
|
1438
|
-
}
|
|
1439
|
-
const key = `group:${originator}`;
|
|
1440
|
-
await this.joinOrCreateGroupedRequest(key, originator, permissionsToRequest, currentRequest.displayOriginator);
|
|
1441
|
-
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1442
|
-
return satisfied ? true : null;
|
|
1443
|
-
}
|
|
1444
|
-
/**
|
|
1445
|
-
* Joins an existing grouped permission request (piggybacks on the existing promise), or creates
|
|
1446
|
-
* a new one and fires the onGroupedPermissionRequested event.
|
|
1447
|
-
*/
|
|
1448
|
-
async joinOrCreateGroupedRequest(key, originator, permissionsToRequest, displayOriginator) {
|
|
1449
|
-
const existing = this.activeRequests.get(key);
|
|
1450
|
-
if (existing == null) {
|
|
1451
|
-
const permPromise = new Promise((resolve, reject) => {
|
|
1452
|
-
this.activeRequests.set(key, {
|
|
1453
|
-
request: { originator, permissions: permissionsToRequest, displayOriginator },
|
|
1454
|
-
pending: [{ resolve, reject }]
|
|
1455
|
-
});
|
|
1456
|
-
});
|
|
1457
|
-
await this.callEvent('onGroupedPermissionRequested', {
|
|
1458
|
-
requestID: key,
|
|
1459
|
-
originator,
|
|
1460
|
-
permissions: permissionsToRequest
|
|
1461
|
-
});
|
|
1462
|
-
await permPromise;
|
|
1463
|
-
}
|
|
1464
|
-
else {
|
|
1465
|
-
const existingRequest = existing.request;
|
|
1466
|
-
existingRequest.permissions.protocolPermissions ??= [];
|
|
1467
|
-
for (const p of permissionsToRequest.protocolPermissions || []) {
|
|
1468
|
-
if (!existingRequest.permissions.protocolPermissions.some(x => deepEqual(x, p))) {
|
|
1469
|
-
existingRequest.permissions.protocolPermissions.push(p);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
await new Promise((resolve, reject) => {
|
|
1473
|
-
existing.pending.push({ resolve, reject });
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
/**
|
|
1478
|
-
* A central method that triggers the permission request flow.
|
|
1479
|
-
* - It checks if there's already an active request for the same key
|
|
1480
|
-
* - If so, we wait on that existing request rather than creating a duplicative one
|
|
1481
|
-
* - Otherwise we create a new request queue, call the relevant "onXXXRequested" event,
|
|
1482
|
-
* and return a promise that resolves once permission is granted or rejects if denied.
|
|
1483
|
-
*/
|
|
1484
|
-
async requestPermissionFlow(r) {
|
|
1485
|
-
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1486
|
-
const preparedRequest = {
|
|
1487
|
-
...r,
|
|
1488
|
-
originator: normalizedOriginator,
|
|
1489
|
-
displayOriginator: r.displayOriginator ?? r.previousToken?.rawOriginator ?? r.originator
|
|
1490
|
-
};
|
|
1491
|
-
if (preparedRequest.type === 'protocol' && preparedRequest.protocolID?.[0] === 1) {
|
|
1492
|
-
preparedRequest.counterparty = '';
|
|
1493
|
-
}
|
|
1494
|
-
const key = this.buildActiveRequestKey(preparedRequest);
|
|
1495
|
-
// If there's already a queue for the same resource, we piggyback on it
|
|
1496
|
-
if (this.isPiggybacking(key)) {
|
|
1497
|
-
return await this.piggybackOnExistingQueue(key);
|
|
1498
|
-
}
|
|
1499
|
-
const pactResult = await this.maybeRequestPact(preparedRequest);
|
|
1500
|
-
if (pactResult !== null)
|
|
1501
|
-
return pactResult;
|
|
1502
|
-
const peerGroupResult = await this.maybeRequestPeerGroupedLevel2ProtocolPermissions(preparedRequest);
|
|
1503
|
-
if (peerGroupResult !== null)
|
|
1504
|
-
return peerGroupResult;
|
|
1505
|
-
const groupResult = await this.resolveGroupedFlow(preparedRequest, key);
|
|
1506
|
-
if (groupResult !== null)
|
|
1507
|
-
return groupResult;
|
|
1508
|
-
// Re-check after grouped flow — another waiter may have set up a queue
|
|
1509
|
-
if (this.isPiggybacking(key))
|
|
1510
|
-
return await this.piggybackOnExistingQueue(key);
|
|
1511
|
-
return await this.enqueueAndFireEvent(preparedRequest, key);
|
|
1512
|
-
}
|
|
1513
|
-
/** Returns true when an active-request queue already has pending waiters for this key. */
|
|
1514
|
-
isPiggybacking(key) {
|
|
1515
|
-
const q = this.activeRequests.get(key);
|
|
1516
|
-
return (q != null) && q.pending.length > 0;
|
|
1517
|
-
}
|
|
1518
|
-
/** Appends to an existing request queue and waits for resolution. */
|
|
1519
|
-
async piggybackOnExistingQueue(key) {
|
|
1520
|
-
const q = this.activeRequests.get(key);
|
|
1521
|
-
return await new Promise((resolve, reject) => {
|
|
1522
|
-
q.pending.push({ resolve, reject });
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
/** Runs the grouped-permission flow (with or without a lock) and checks post-group satisfaction. */
|
|
1526
|
-
async resolveGroupedFlow(preparedRequest, key) {
|
|
1527
|
-
const hadPendingGroupedFlowBefore = this.config.seekGroupedPermission && this.groupedPermissionFlowTail.has(preparedRequest.originator);
|
|
1528
|
-
let groupResult;
|
|
1529
|
-
if (this.config.seekGroupedPermission) {
|
|
1530
|
-
groupResult = await this.withGroupedPermissionFlowLock(preparedRequest.originator, async () => {
|
|
1531
|
-
return await this.maybeRequestGroupedPermissions(preparedRequest);
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
else {
|
|
1535
|
-
groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
|
|
1536
|
-
}
|
|
1537
|
-
if (groupResult !== null)
|
|
1538
|
-
return groupResult;
|
|
1539
|
-
if (hadPendingGroupedFlowBefore) {
|
|
1540
|
-
const satisfiedAfterGroup = await this.checkSpecificPermissionAfterGroupFlow(preparedRequest);
|
|
1541
|
-
if (satisfiedAfterGroup)
|
|
1542
|
-
return true;
|
|
1543
|
-
}
|
|
1544
|
-
return null;
|
|
1545
|
-
}
|
|
1546
|
-
/** Creates a new active-request queue entry, fires the event, and returns the promise. */
|
|
1547
|
-
async enqueueAndFireEvent(preparedRequest, key) {
|
|
1548
|
-
const requestPromise = new Promise((resolve, reject) => {
|
|
1549
|
-
this.activeRequests.set(key, { request: preparedRequest, pending: [{ resolve, reject }] });
|
|
1550
|
-
});
|
|
1551
|
-
try {
|
|
1552
|
-
await this.firePermissionRequestEvent(preparedRequest, key);
|
|
1553
|
-
}
|
|
1554
|
-
catch (e) {
|
|
1555
|
-
const matching = this.activeRequests.get(key);
|
|
1556
|
-
if (matching != null) {
|
|
1557
|
-
for (const p of matching.pending)
|
|
1558
|
-
p.reject(e);
|
|
1559
|
-
this.activeRequests.delete(key);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
return await requestPromise;
|
|
1563
|
-
}
|
|
1564
|
-
/** Fires the appropriate onXXXRequested event based on the request type. */
|
|
1565
|
-
async firePermissionRequestEvent(request, key) {
|
|
1566
|
-
switch (request.type) {
|
|
1567
|
-
case 'protocol':
|
|
1568
|
-
await this.callEvent('onProtocolPermissionRequested', {
|
|
1569
|
-
...request,
|
|
1570
|
-
counterparty: request.protocolID?.[0] === 1 ? undefined : request.counterparty,
|
|
1571
|
-
requestID: key
|
|
1572
|
-
});
|
|
1573
|
-
break;
|
|
1574
|
-
case 'basket':
|
|
1575
|
-
await this.callEvent('onBasketAccessRequested', { ...request, requestID: key });
|
|
1576
|
-
break;
|
|
1577
|
-
case 'certificate':
|
|
1578
|
-
await this.callEvent('onCertificateAccessRequested', { ...request, requestID: key });
|
|
1579
|
-
break;
|
|
1580
|
-
case 'spending':
|
|
1581
|
-
await this.callEvent('onSpendingAuthorizationRequested', { ...request, requestID: key });
|
|
1582
|
-
break;
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
/* ---------------------------------------------------------------------
|
|
1586
|
-
* 4) SEARCH / DECODE / DECRYPT ON-CHAIN TOKENS (PushDrop Scripts)
|
|
1587
|
-
* --------------------------------------------------------------------- */
|
|
1588
|
-
/**
|
|
1589
|
-
* We will use a administrative "permission token encryption" protocol to store fields
|
|
1590
|
-
* in each permission's PushDrop script. This ensures that only the user's wallet
|
|
1591
|
-
* can decrypt them. In practice, this data is not super sensitive, but we still
|
|
1592
|
-
* follow the principle of least exposure.
|
|
1593
|
-
*/
|
|
1594
|
-
static PERM_TOKEN_ENCRYPTION_PROTOCOL = [
|
|
1595
|
-
2,
|
|
1596
|
-
'admin permission token encryption'
|
|
1597
|
-
];
|
|
1598
|
-
/**
|
|
1599
|
-
* Similarly, we will use a "metadata encryption" protocol to preserve the confidentiality
|
|
1600
|
-
* of transaction descriptions and input/output descriptions from lower storage layers.
|
|
1601
|
-
*/
|
|
1602
|
-
static METADATA_ENCRYPTION_PROTOCOL = [
|
|
1603
|
-
2,
|
|
1604
|
-
'admin metadata encryption'
|
|
1605
|
-
];
|
|
1606
|
-
/** We always use `keyID="1"` and `counterparty="self"` for these encryption ops. */
|
|
1607
|
-
async encryptPermissionTokenField(plaintext) {
|
|
1608
|
-
const data = typeof plaintext === 'string' ? sdk_1.Utils.toArray(plaintext, 'utf8') : plaintext;
|
|
1609
|
-
const { ciphertext } = await this.underlying.encrypt({
|
|
1610
|
-
plaintext: data,
|
|
1611
|
-
protocolID: WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
1612
|
-
keyID: '1'
|
|
1613
|
-
}, this.adminOriginator);
|
|
1614
|
-
return ciphertext;
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Extracts a transaction from a `listOutputs` result's BEEF bundle, parsing
|
|
1618
|
-
* the bundle only once per result. Calling
|
|
1619
|
-
* `Transaction.fromBEEF(result.BEEF, txid)` per output re-parses the entire
|
|
1620
|
-
* bundle every time — O(n²) in the number of outputs — which can block the
|
|
1621
|
-
* caller's thread (in browser wallets, the UI) for seconds on large
|
|
1622
|
-
* permission baskets.
|
|
1623
|
-
*/
|
|
1624
|
-
transactionFromResultBeef(result, txid) {
|
|
1625
|
-
const beef = result.BEEF;
|
|
1626
|
-
if (beef != null) {
|
|
1627
|
-
let parsed = this.parsedBeefCache.get(beef);
|
|
1628
|
-
if (parsed === undefined) {
|
|
1629
|
-
try {
|
|
1630
|
-
parsed = sdk_1.Beef.fromBinary(beef);
|
|
1631
|
-
}
|
|
1632
|
-
catch {
|
|
1633
|
-
parsed = null;
|
|
1634
|
-
}
|
|
1635
|
-
this.parsedBeefCache.set(beef, parsed);
|
|
1636
|
-
}
|
|
1637
|
-
// findAtomicTransaction (not findTxid().tx) matches fromBEEF exactly:
|
|
1638
|
-
// it attaches merkle paths and sourceTransaction ancestry, which token
|
|
1639
|
-
// consumers rely on when re-serializing via tx.toBEEF().
|
|
1640
|
-
const tx = parsed?.findAtomicTransaction(txid);
|
|
1641
|
-
if (tx != null)
|
|
1642
|
-
return tx;
|
|
1643
|
-
}
|
|
1644
|
-
// Preserve the original behavior (including throwing) for bundles the
|
|
1645
|
-
// fast path can't serve.
|
|
1646
|
-
return sdk_1.Transaction.fromBEEF(beef ?? [], txid);
|
|
1647
|
-
}
|
|
1648
|
-
async decryptPermissionTokenField(ciphertext) {
|
|
1649
|
-
// Field decrypts run in tight loops over every token in a basket; yield a
|
|
1650
|
-
// macrotask periodically so large scans don't starve the caller's event
|
|
1651
|
-
// loop (in browser wallets, that means blocking rendering and input).
|
|
1652
|
-
if (++this.tokenFieldDecryptCount % 8 === 0) {
|
|
1653
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1654
|
-
}
|
|
1655
|
-
try {
|
|
1656
|
-
const { plaintext } = await this.underlying.decrypt({
|
|
1657
|
-
ciphertext,
|
|
1658
|
-
protocolID: WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
1659
|
-
keyID: '1'
|
|
1660
|
-
}, this.adminOriginator);
|
|
1661
|
-
return plaintext;
|
|
1662
|
-
}
|
|
1663
|
-
catch (_decryptionError) {
|
|
1664
|
-
// Decryption failed (e.g. wrong key, unencrypted legacy value) — return the raw bytes unchanged
|
|
1665
|
-
return ciphertext;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Encrypts wallet metadata if configured to do so, otherwise returns the original plaintext for storage.
|
|
1670
|
-
* @param plaintext The metadata to encrypt if configured to do so
|
|
1671
|
-
* @returns The encrypted metadata, or the original value if encryption was disabled.
|
|
1672
|
-
*/
|
|
1673
|
-
async maybeEncryptMetadata(plaintext) {
|
|
1674
|
-
if (!this.config.encryptWalletMetadata) {
|
|
1675
|
-
return plaintext;
|
|
1676
|
-
}
|
|
1677
|
-
const { ciphertext } = await this.underlying.encrypt({
|
|
1678
|
-
plaintext: sdk_1.Utils.toArray(plaintext, 'utf8'),
|
|
1679
|
-
protocolID: WalletPermissionsManager.METADATA_ENCRYPTION_PROTOCOL,
|
|
1680
|
-
keyID: '1'
|
|
1681
|
-
}, this.adminOriginator);
|
|
1682
|
-
return sdk_1.Utils.toBase64(ciphertext);
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Attempts to decrypt metadata. if decryption fails, assumes the value is already plaintext and returns it.
|
|
1686
|
-
* @param ciphertext The metadata to attempt decryption for.
|
|
1687
|
-
* @returns The decrypted metadata. If decryption fails, returns the original value instead.
|
|
1688
|
-
*/
|
|
1689
|
-
async maybeDecryptMetadata(ciphertext) {
|
|
1690
|
-
try {
|
|
1691
|
-
const { plaintext } = await this.underlying.decrypt({
|
|
1692
|
-
ciphertext: sdk_1.Utils.toArray(ciphertext, 'base64'),
|
|
1693
|
-
protocolID: WalletPermissionsManager.METADATA_ENCRYPTION_PROTOCOL,
|
|
1694
|
-
keyID: '1'
|
|
1695
|
-
}, this.adminOriginator);
|
|
1696
|
-
return sdk_1.Utils.toUTF8(plaintext);
|
|
1697
|
-
}
|
|
1698
|
-
catch (_decryptionError) {
|
|
1699
|
-
// Decryption failed (e.g. wrong key, unencrypted legacy value) — return original string unchanged
|
|
1700
|
-
return ciphertext;
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
/** Helper to see if a token's expiry is in the past. */
|
|
1704
|
-
isTokenExpired(expiry) {
|
|
1705
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1706
|
-
return expiry > 0 && expiry < now;
|
|
1707
|
-
}
|
|
1708
|
-
/** Decrypts the standard 6 fields from a protocol (DPACP) PushDrop script. */
|
|
1709
|
-
async decryptProtocolTokenFields(fields) {
|
|
1710
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[0]));
|
|
1711
|
-
const expiryDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[1])), 10);
|
|
1712
|
-
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[2])) === 'true';
|
|
1713
|
-
const secLevelDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[3])), 10);
|
|
1714
|
-
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[4]));
|
|
1715
|
-
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fields[5]));
|
|
1716
|
-
return { domainDecoded, expiryDecoded, privDecoded, secLevelDecoded, protoNameDecoded, cptyDecoded };
|
|
1717
|
-
}
|
|
1718
|
-
/** Parses outpoint string "txid.vout" into [txid, outputIndex]. */
|
|
1719
|
-
parseOutpoint(outpoint) {
|
|
1720
|
-
const [txid, indexStr] = outpoint.split('.');
|
|
1721
|
-
return [txid, Number.parseInt(indexStr, 10)];
|
|
1722
|
-
}
|
|
1723
|
-
/** Normalizes a txid string to lowercase. */
|
|
1724
|
-
normalizeTxid(txid) {
|
|
1725
|
-
return (txid ?? '').toLowerCase();
|
|
1726
|
-
}
|
|
1727
|
-
/** Reverses a 32-byte hex txid (for endian normalization). */
|
|
1728
|
-
reverseHexTxid(txid) {
|
|
1729
|
-
const hex = this.normalizeTxid(txid);
|
|
1730
|
-
if (!/^[0-9a-f]{64}$/.test(hex))
|
|
1731
|
-
return hex;
|
|
1732
|
-
const bytes = hex.match(/../g);
|
|
1733
|
-
return (bytes != null) ? bytes.reverse().join('') : hex;
|
|
1734
|
-
}
|
|
1735
|
-
/**
|
|
1736
|
-
* Returns true when an outpoint string (e.g. "txid.vout" or "txid:vout") refers to `token`.
|
|
1737
|
-
*/
|
|
1738
|
-
tokenMatchesOutpointString(outpoint, token) {
|
|
1739
|
-
const dot = outpoint.lastIndexOf('.');
|
|
1740
|
-
const colon = outpoint.lastIndexOf(':');
|
|
1741
|
-
const sep = Math.max(dot, colon);
|
|
1742
|
-
if (sep === -1)
|
|
1743
|
-
return false;
|
|
1744
|
-
const txidPart = outpoint.slice(0, sep);
|
|
1745
|
-
const vout = Number(outpoint.slice(sep + 1));
|
|
1746
|
-
if (!Number.isFinite(vout))
|
|
1747
|
-
return false;
|
|
1748
|
-
return this.normalizeTxid(txidPart) === this.normalizeTxid(token.txid) && vout === token.outputIndex;
|
|
1749
|
-
}
|
|
1750
|
-
/**
|
|
1751
|
-
* Finds the index of the tx input that spends the given permission token.
|
|
1752
|
-
* Handles multiple potential field names for TXID and vout.
|
|
1753
|
-
*/
|
|
1754
|
-
findInputIndexForToken(tx, token) {
|
|
1755
|
-
return tx.inputs.findIndex((input) => {
|
|
1756
|
-
const txidCandidate = input?.sourceTXID ??
|
|
1757
|
-
input?.sourceTxid ??
|
|
1758
|
-
input?.sourceTxId ??
|
|
1759
|
-
input?.prevTxId ??
|
|
1760
|
-
input?.prevTxid ??
|
|
1761
|
-
input?.prevTXID ??
|
|
1762
|
-
input?.txid ??
|
|
1763
|
-
input?.txID;
|
|
1764
|
-
const voutCandidate = input?.sourceOutputIndex ?? input?.sourceOutput ?? input?.outputIndex ?? input?.vout ?? input?.prevOutIndex;
|
|
1765
|
-
if (typeof txidCandidate === 'string' && typeof voutCandidate === 'number') {
|
|
1766
|
-
const cand = this.normalizeTxid(txidCandidate);
|
|
1767
|
-
if (cand === this.normalizeTxid(token.txid) && voutCandidate === token.outputIndex)
|
|
1768
|
-
return true;
|
|
1769
|
-
if (cand === this.reverseHexTxid(token.txid) && voutCandidate === token.outputIndex)
|
|
1770
|
-
return true;
|
|
1771
|
-
}
|
|
1772
|
-
const outpointCandidate = input?.outpoint ?? input?.sourceOutpoint ?? input?.prevOutpoint;
|
|
1773
|
-
return typeof outpointCandidate === 'string' && this.tokenMatchesOutpointString(outpointCandidate, token);
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
/** Looks for a DPACP permission token matching origin/domain, privileged, protocol, cpty. */
|
|
1777
|
-
async findProtocolToken(originator, privileged, protocolID, counterparty, includeExpired, originatorLookupValues) {
|
|
1778
|
-
const [secLevel, protoName] = protocolID;
|
|
1779
|
-
const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator];
|
|
1780
|
-
for (const originTag of originsToTry) {
|
|
1781
|
-
const tags = [
|
|
1782
|
-
`originator ${originTag}`,
|
|
1783
|
-
`privileged ${!!privileged}`,
|
|
1784
|
-
`protocolName ${protoName}`,
|
|
1785
|
-
`protocolSecurityLevel ${secLevel}`
|
|
1786
|
-
];
|
|
1787
|
-
if (secLevel === 2) {
|
|
1788
|
-
tags.push(`counterparty ${counterparty}`);
|
|
1789
|
-
}
|
|
1790
|
-
const result = await this.underlying.listOutputs({
|
|
1791
|
-
basket: BASKET_MAP.protocol,
|
|
1792
|
-
tags,
|
|
1793
|
-
tagQueryMode: 'all',
|
|
1794
|
-
include: 'entire transactions'
|
|
1795
|
-
}, this.adminOriginator);
|
|
1796
|
-
for (const out of result.outputs) {
|
|
1797
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
1798
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
1799
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
1800
|
-
if (dec?.fields == null || dec.fields.length < 6)
|
|
1801
|
-
continue;
|
|
1802
|
-
const f = await this.decryptProtocolTokenFields(dec.fields);
|
|
1803
|
-
if (this.normalizeOriginator(f.domainDecoded) !== originator)
|
|
1804
|
-
continue;
|
|
1805
|
-
if (f.privDecoded !== !!privileged ||
|
|
1806
|
-
f.secLevelDecoded !== secLevel ||
|
|
1807
|
-
f.protoNameDecoded !== protoName ||
|
|
1808
|
-
(f.secLevelDecoded === 2 && f.cptyDecoded !== counterparty)) {
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
if (!includeExpired && this.isTokenExpired(f.expiryDecoded))
|
|
1812
|
-
continue;
|
|
1813
|
-
return {
|
|
1814
|
-
tx: tx.toBEEF(),
|
|
1815
|
-
txid,
|
|
1816
|
-
outputIndex,
|
|
1817
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
1818
|
-
satoshis: out.satoshis,
|
|
1819
|
-
originator,
|
|
1820
|
-
rawOriginator: f.domainDecoded,
|
|
1821
|
-
privileged,
|
|
1822
|
-
protocol: protoName,
|
|
1823
|
-
securityLevel: secLevel,
|
|
1824
|
-
expiry: f.expiryDecoded,
|
|
1825
|
-
counterparty: f.cptyDecoded
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
return undefined;
|
|
1830
|
-
}
|
|
1831
|
-
/** Finds ALL DPACP permission tokens matching origin/domain, privileged, protocol, cpty. Never filters by expiry. */
|
|
1832
|
-
async findAllProtocolTokens(originator, privileged, protocolID, counterparty, originatorLookupValues) {
|
|
1833
|
-
const [secLevel, protoName] = protocolID;
|
|
1834
|
-
const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator];
|
|
1835
|
-
const matches = [];
|
|
1836
|
-
const seen = new Set();
|
|
1837
|
-
for (const originTag of originsToTry) {
|
|
1838
|
-
const tags = [
|
|
1839
|
-
`originator ${originTag}`,
|
|
1840
|
-
`privileged ${!!privileged}`,
|
|
1841
|
-
`protocolName ${protoName}`,
|
|
1842
|
-
`protocolSecurityLevel ${secLevel}`
|
|
1843
|
-
];
|
|
1844
|
-
if (secLevel === 2) {
|
|
1845
|
-
tags.push(`counterparty ${counterparty}`);
|
|
1846
|
-
}
|
|
1847
|
-
const result = await this.underlying.listOutputs({
|
|
1848
|
-
basket: BASKET_MAP.protocol,
|
|
1849
|
-
tags,
|
|
1850
|
-
tagQueryMode: 'all',
|
|
1851
|
-
include: 'entire transactions'
|
|
1852
|
-
}, this.adminOriginator);
|
|
1853
|
-
for (const out of result.outputs) {
|
|
1854
|
-
if (seen.has(out.outpoint))
|
|
1855
|
-
continue;
|
|
1856
|
-
const [txid, vout] = this.parseOutpoint(out.outpoint);
|
|
1857
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
1858
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[vout].lockingScript);
|
|
1859
|
-
if (dec?.fields == null || dec.fields.length < 6)
|
|
1860
|
-
continue;
|
|
1861
|
-
const f = await this.decryptProtocolTokenFields(dec.fields);
|
|
1862
|
-
if (this.normalizeOriginator(f.domainDecoded) !== originator)
|
|
1863
|
-
continue;
|
|
1864
|
-
if (f.privDecoded !== !!privileged ||
|
|
1865
|
-
f.secLevelDecoded !== secLevel ||
|
|
1866
|
-
f.protoNameDecoded !== protoName ||
|
|
1867
|
-
(f.secLevelDecoded === 2 && f.cptyDecoded !== counterparty)) {
|
|
1868
|
-
continue;
|
|
1869
|
-
}
|
|
1870
|
-
seen.add(out.outpoint);
|
|
1871
|
-
matches.push({
|
|
1872
|
-
tx: tx.toBEEF(),
|
|
1873
|
-
txid,
|
|
1874
|
-
outputIndex: vout,
|
|
1875
|
-
outputScript: tx.outputs[vout].lockingScript.toHex(),
|
|
1876
|
-
satoshis: out.satoshis,
|
|
1877
|
-
originator,
|
|
1878
|
-
rawOriginator: f.domainDecoded,
|
|
1879
|
-
privileged,
|
|
1880
|
-
protocol: protoName,
|
|
1881
|
-
securityLevel: secLevel,
|
|
1882
|
-
expiry: f.expiryDecoded,
|
|
1883
|
-
counterparty: f.cptyDecoded
|
|
1884
|
-
});
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
return matches;
|
|
1888
|
-
}
|
|
1889
|
-
/** Looks for a DBAP token matching (originator, basket). */
|
|
1890
|
-
async findBasketToken(originator, basket, includeExpired, originatorLookupValues) {
|
|
1891
|
-
const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator];
|
|
1892
|
-
for (const originTag of originsToTry) {
|
|
1893
|
-
const result = await this.underlying.listOutputs({
|
|
1894
|
-
basket: BASKET_MAP.basket,
|
|
1895
|
-
tags: [`originator ${originTag}`, `basket ${basket}`],
|
|
1896
|
-
tagQueryMode: 'all',
|
|
1897
|
-
include: 'entire transactions'
|
|
1898
|
-
}, this.adminOriginator);
|
|
1899
|
-
for (const out of result.outputs) {
|
|
1900
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
1901
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
1902
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
1903
|
-
if (!dec?.fields || dec.fields.length < 3)
|
|
1904
|
-
continue;
|
|
1905
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(dec.fields[0]));
|
|
1906
|
-
if (this.normalizeOriginator(domainDecoded) !== originator)
|
|
1907
|
-
continue;
|
|
1908
|
-
const expiryDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(dec.fields[1])), 10);
|
|
1909
|
-
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(dec.fields[2]));
|
|
1910
|
-
if (basketDecoded !== basket)
|
|
1911
|
-
continue;
|
|
1912
|
-
if (!includeExpired && this.isTokenExpired(expiryDecoded))
|
|
1913
|
-
continue;
|
|
1914
|
-
return {
|
|
1915
|
-
tx: tx.toBEEF(),
|
|
1916
|
-
txid,
|
|
1917
|
-
outputIndex,
|
|
1918
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
1919
|
-
satoshis: out.satoshis,
|
|
1920
|
-
originator,
|
|
1921
|
-
rawOriginator: domainDecoded,
|
|
1922
|
-
basketName: basketDecoded,
|
|
1923
|
-
expiry: expiryDecoded
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
return undefined;
|
|
1928
|
-
}
|
|
1929
|
-
/** Looks for a DCAP token matching (origin, privileged, verifier, certType, fields subset). */
|
|
1930
|
-
async findCertificateToken(originator, privileged, verifier, certType, fields, includeExpired, originatorLookupValues) {
|
|
1931
|
-
const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator];
|
|
1932
|
-
for (const originTag of originsToTry) {
|
|
1933
|
-
const result = await this.underlying.listOutputs({
|
|
1934
|
-
basket: BASKET_MAP.certificate,
|
|
1935
|
-
tags: [`originator ${originTag}`, `privileged ${!!privileged}`, `type ${certType}`, `verifier ${verifier}`],
|
|
1936
|
-
tagQueryMode: 'all',
|
|
1937
|
-
include: 'entire transactions'
|
|
1938
|
-
}, this.adminOriginator);
|
|
1939
|
-
for (const out of result.outputs) {
|
|
1940
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
1941
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
1942
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
1943
|
-
if (!dec?.fields || dec.fields.length < 6)
|
|
1944
|
-
continue;
|
|
1945
|
-
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
1946
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1947
|
-
if (this.normalizeOriginator(domainDecoded) !== originator)
|
|
1948
|
-
continue;
|
|
1949
|
-
const expiryDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1950
|
-
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1951
|
-
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
1952
|
-
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
1953
|
-
const allFields = JSON.parse(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fieldsRaw)));
|
|
1954
|
-
if (privDecoded !== !!privileged || typeDecoded !== certType || verifierDec !== verifier)
|
|
1955
|
-
continue;
|
|
1956
|
-
// Check if 'fields' is a subset of 'allFields'
|
|
1957
|
-
const setAll = new Set(allFields);
|
|
1958
|
-
if (fields.some(f => !setAll.has(f)))
|
|
1959
|
-
continue;
|
|
1960
|
-
if (!includeExpired && this.isTokenExpired(expiryDecoded))
|
|
1961
|
-
continue;
|
|
1962
|
-
return {
|
|
1963
|
-
tx: tx.toBEEF(),
|
|
1964
|
-
txid,
|
|
1965
|
-
outputIndex,
|
|
1966
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
1967
|
-
satoshis: out.satoshis,
|
|
1968
|
-
originator,
|
|
1969
|
-
rawOriginator: domainDecoded,
|
|
1970
|
-
privileged,
|
|
1971
|
-
verifier: verifierDec,
|
|
1972
|
-
certType: typeDecoded,
|
|
1973
|
-
certFields: allFields,
|
|
1974
|
-
expiry: expiryDecoded
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
return undefined;
|
|
1979
|
-
}
|
|
1980
|
-
/** Looks for a DSAP token matching origin, returning the first one found. */
|
|
1981
|
-
async findSpendingToken(originator, originatorLookupValues) {
|
|
1982
|
-
const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator];
|
|
1983
|
-
for (const originTag of originsToTry) {
|
|
1984
|
-
const result = await this.underlying.listOutputs({
|
|
1985
|
-
basket: BASKET_MAP.spending,
|
|
1986
|
-
tags: [`originator ${originTag}`],
|
|
1987
|
-
tagQueryMode: 'all',
|
|
1988
|
-
include: 'entire transactions'
|
|
1989
|
-
}, this.adminOriginator);
|
|
1990
|
-
for (const out of result.outputs) {
|
|
1991
|
-
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1992
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
1993
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1994
|
-
if (!dec?.fields || dec.fields.length < 2)
|
|
1995
|
-
continue;
|
|
1996
|
-
const domainRaw = dec.fields[0];
|
|
1997
|
-
const amtRaw = dec.fields[1];
|
|
1998
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1999
|
-
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
2000
|
-
if (normalizedDomain !== originator)
|
|
2001
|
-
continue;
|
|
2002
|
-
const amtDecodedStr = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw));
|
|
2003
|
-
const authorizedAmount = Number.parseInt(amtDecodedStr, 10);
|
|
2004
|
-
return {
|
|
2005
|
-
tx: tx.toBEEF(),
|
|
2006
|
-
txid: out.outpoint.split('.')[0],
|
|
2007
|
-
outputIndex: Number.parseInt(out.outpoint.split('.')[1], 10),
|
|
2008
|
-
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
2009
|
-
satoshis: out.satoshis,
|
|
2010
|
-
originator,
|
|
2011
|
-
rawOriginator: domainDecoded,
|
|
2012
|
-
authorizedAmount,
|
|
2013
|
-
expiry: 0 // Not time-limited, monthly authorization
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
return undefined;
|
|
2018
|
-
}
|
|
2019
|
-
/**
|
|
2020
|
-
* Returns the current month and year in UTC as a string in the format "YYYY-MM".
|
|
2021
|
-
*
|
|
2022
|
-
* @returns {string} The current month and year in UTC.
|
|
2023
|
-
*/
|
|
2024
|
-
getCurrentMonthYearUTC() {
|
|
2025
|
-
const now = new Date();
|
|
2026
|
-
const year = now.getUTCFullYear();
|
|
2027
|
-
const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); // Ensure 2-digit month
|
|
2028
|
-
return `${year}-${month}`;
|
|
2029
|
-
}
|
|
2030
|
-
/**
|
|
2031
|
-
* Returns spending for an originator in the current calendar month.
|
|
2032
|
-
*/
|
|
2033
|
-
async querySpentSince(token) {
|
|
2034
|
-
const labelOrigins = this.buildOriginatorLookupValues(token.rawOriginator, token.originator);
|
|
2035
|
-
let total = 0;
|
|
2036
|
-
for (const labelOrigin of labelOrigins) {
|
|
2037
|
-
const { actions } = await this.underlying.listActions({
|
|
2038
|
-
labels: [`admin originator ${labelOrigin}`, `admin month ${this.getCurrentMonthYearUTC()}`],
|
|
2039
|
-
labelQueryMode: 'all'
|
|
2040
|
-
}, this.adminOriginator);
|
|
2041
|
-
total += actions.reduce((a, e) => a - e.satoshis, 0);
|
|
2042
|
-
}
|
|
2043
|
-
return total;
|
|
2044
|
-
}
|
|
2045
|
-
/* ---------------------------------------------------------------------
|
|
2046
|
-
* 5) CREATE / RENEW / REVOKE PERMISSION TOKENS ON CHAIN
|
|
2047
|
-
* --------------------------------------------------------------------- */
|
|
2048
|
-
/**
|
|
2049
|
-
* Creates a brand-new permission token as a single-output PushDrop script in the relevant admin basket.
|
|
2050
|
-
*
|
|
2051
|
-
* The main difference between each type of token is in the "fields" we store in the PushDrop script.
|
|
2052
|
-
*
|
|
2053
|
-
* @param r The permission request
|
|
2054
|
-
* @param expiry The expiry epoch time
|
|
2055
|
-
* @param amount For DSAP, the authorized spending limit
|
|
2056
|
-
*/
|
|
2057
|
-
async createPermissionOnChain(r, expiry, amount) {
|
|
2058
|
-
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
2059
|
-
r.originator = normalizedOriginator;
|
|
2060
|
-
const basketName = BASKET_MAP[r.type];
|
|
2061
|
-
if (!basketName)
|
|
2062
|
-
return;
|
|
2063
|
-
// Build the array of encrypted fields for the PushDrop script
|
|
2064
|
-
const fields = await this.buildPushdropFields(r, expiry, amount);
|
|
2065
|
-
// Construct the script. We do a simple P2PK check. We ask `PushDrop.lock(...)`
|
|
2066
|
-
// to create a script with a single OP_CHECKSIG verifying ownership to redeem.
|
|
2067
|
-
const script = await new sdk_1.PushDrop(this.underlying).lock(fields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
2068
|
-
// Create tags
|
|
2069
|
-
const tags = this.buildTagsForRequest(r);
|
|
2070
|
-
// Build a transaction with exactly one output, no explicit inputs since the wallet
|
|
2071
|
-
// can internally fund it from its balance.
|
|
2072
|
-
await this.createAction({
|
|
2073
|
-
description: `Grant ${r.type} permission`,
|
|
2074
|
-
outputs: [
|
|
2075
|
-
{
|
|
2076
|
-
lockingScript: script.toHex(),
|
|
2077
|
-
satoshis: 1,
|
|
2078
|
-
outputDescription: `${r.type} permission token`,
|
|
2079
|
-
basket: basketName,
|
|
2080
|
-
tags
|
|
2081
|
-
}
|
|
2082
|
-
],
|
|
2083
|
-
options: {
|
|
2084
|
-
acceptDelayedBroadcast: true
|
|
2085
|
-
}
|
|
2086
|
-
}, this.adminOriginator);
|
|
2087
|
-
}
|
|
2088
|
-
async mapWithConcurrency(items, concurrency, fn) {
|
|
2089
|
-
if (items.length === 0)
|
|
2090
|
-
return [];
|
|
2091
|
-
const results = new Array(items.length);
|
|
2092
|
-
let i = 0;
|
|
2093
|
-
const worker = async () => {
|
|
2094
|
-
while (true) {
|
|
2095
|
-
const idx = i++;
|
|
2096
|
-
if (idx >= items.length)
|
|
2097
|
-
return;
|
|
2098
|
-
results[idx] = await fn(items[idx]);
|
|
2099
|
-
}
|
|
2100
|
-
};
|
|
2101
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => await worker()));
|
|
2102
|
-
return results;
|
|
2103
|
-
}
|
|
2104
|
-
async runBestEffortBatches(items, chunkSize, runChunk) {
|
|
2105
|
-
if (items.length === 0)
|
|
2106
|
-
return [];
|
|
2107
|
-
const out = [];
|
|
2108
|
-
for (let i = 0; i < items.length; i += chunkSize) {
|
|
2109
|
-
const chunk = items.slice(i, i + chunkSize);
|
|
2110
|
-
out.push(...(await this.runBestEffortChunk(chunk, runChunk)));
|
|
2111
|
-
}
|
|
2112
|
-
return out;
|
|
2113
|
-
}
|
|
2114
|
-
async runBestEffortChunk(chunk, runChunk) {
|
|
2115
|
-
try {
|
|
2116
|
-
return await runChunk(chunk);
|
|
2117
|
-
}
|
|
2118
|
-
catch (e) {
|
|
2119
|
-
if (chunk.length <= 1) {
|
|
2120
|
-
console.error('Permission batch failed:', e);
|
|
2121
|
-
return [];
|
|
2122
|
-
}
|
|
2123
|
-
const mid = Math.ceil(chunk.length / 2);
|
|
2124
|
-
const left = await this.runBestEffortChunk(chunk.slice(0, mid), runChunk);
|
|
2125
|
-
const right = await this.runBestEffortChunk(chunk.slice(mid), runChunk);
|
|
2126
|
-
return [...left, ...right];
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
async buildPermissionOutput(r, expiry, amount) {
|
|
2130
|
-
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
2131
|
-
r.originator = normalizedOriginator;
|
|
2132
|
-
const basketName = BASKET_MAP[r.type];
|
|
2133
|
-
if (!basketName) {
|
|
2134
|
-
throw new Error(`Unsupported permission type: ${r.type}`);
|
|
2135
|
-
}
|
|
2136
|
-
const fields = await this.buildPushdropFields(r, expiry, amount);
|
|
2137
|
-
const script = await new sdk_1.PushDrop(this.underlying).lock(fields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
2138
|
-
const tags = this.buildTagsForRequest(r);
|
|
2139
|
-
return {
|
|
2140
|
-
request: r,
|
|
2141
|
-
output: {
|
|
2142
|
-
lockingScript: script.toHex(),
|
|
2143
|
-
satoshis: 1,
|
|
2144
|
-
outputDescription: `${r.type} permission token`,
|
|
2145
|
-
basket: basketName,
|
|
2146
|
-
tags
|
|
2147
|
-
}
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
async createPermissionTokensBestEffort(items) {
|
|
2151
|
-
const CHUNK = 25;
|
|
2152
|
-
return await this.runBestEffortBatches(items, CHUNK, async (chunk) => {
|
|
2153
|
-
const built = await this.mapWithConcurrency(chunk, 8, async (c) => await this.buildPermissionOutput(c.request, c.expiry, c.amount));
|
|
2154
|
-
await this.createAction({
|
|
2155
|
-
description: `Grant ${built.length} permissions`,
|
|
2156
|
-
outputs: built.map(b => b.output),
|
|
2157
|
-
options: { acceptDelayedBroadcast: true }
|
|
2158
|
-
}, this.adminOriginator);
|
|
2159
|
-
return built.map(b => b.request);
|
|
2160
|
-
});
|
|
2161
|
-
}
|
|
2162
|
-
async renewPermissionTokensBestEffort(items) {
|
|
2163
|
-
const CHUNK = 15;
|
|
2164
|
-
return await this.runBestEffortBatches(items, CHUNK, async (chunk) => {
|
|
2165
|
-
const built = await this.mapWithConcurrency(chunk, 8, async (c) => await this.buildPermissionOutput(c.request, c.expiry, c.amount));
|
|
2166
|
-
const inputBeef = new sdk_1.Beef();
|
|
2167
|
-
for (const c of chunk) {
|
|
2168
|
-
inputBeef.mergeBeef(sdk_1.Beef.fromBinary(c.oldToken.tx));
|
|
2169
|
-
}
|
|
2170
|
-
const { signableTransaction } = await this.createAction({
|
|
2171
|
-
description: `Renew ${chunk.length} permissions`,
|
|
2172
|
-
inputBEEF: inputBeef.toBinary(),
|
|
2173
|
-
inputs: chunk.map((c, i) => ({
|
|
2174
|
-
outpoint: `${c.oldToken.txid}.${c.oldToken.outputIndex}`,
|
|
2175
|
-
unlockingScriptLength: 73,
|
|
2176
|
-
inputDescription: `Consume old permission token #${i + 1}`
|
|
2177
|
-
})),
|
|
2178
|
-
outputs: built.map(b => b.output),
|
|
2179
|
-
options: {
|
|
2180
|
-
acceptDelayedBroadcast: true,
|
|
2181
|
-
randomizeOutputs: false,
|
|
2182
|
-
signAndProcess: false
|
|
2183
|
-
}
|
|
2184
|
-
}, this.adminOriginator);
|
|
2185
|
-
if (!signableTransaction?.reference || !signableTransaction.tx) {
|
|
2186
|
-
throw new Error('Failed to create signable transaction');
|
|
2187
|
-
}
|
|
2188
|
-
const partialTx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
|
|
2189
|
-
const pushdrop = new sdk_1.PushDrop(this.underlying);
|
|
2190
|
-
const spends = {};
|
|
2191
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
2192
|
-
const token = chunk[i].oldToken;
|
|
2193
|
-
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
|
|
2194
|
-
const unlockingScript = await unlocker.sign(partialTx, i);
|
|
2195
|
-
spends[i] = { unlockingScript: unlockingScript.toHex() };
|
|
2196
|
-
}
|
|
2197
|
-
const { txid } = await this.underlying.signAction({
|
|
2198
|
-
reference: signableTransaction.reference,
|
|
2199
|
-
spends
|
|
2200
|
-
});
|
|
2201
|
-
if (!txid)
|
|
2202
|
-
throw new Error('Failed to finalize renewal transaction');
|
|
2203
|
-
return built.map(b => b.request);
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
async coalescePermissionTokens(oldTokens, newScript, opts) {
|
|
2207
|
-
if (!oldTokens?.length)
|
|
2208
|
-
throw new Error('No permission tokens to coalesce');
|
|
2209
|
-
if (oldTokens.length < 2)
|
|
2210
|
-
throw new Error('Need at least 2 tokens to coalesce');
|
|
2211
|
-
// 1) Create a signable action with N inputs and a single renewed output
|
|
2212
|
-
// Merge all input token BEEFs into a single BEEF structure
|
|
2213
|
-
const inputBeef = new sdk_1.Beef();
|
|
2214
|
-
for (const token of oldTokens) {
|
|
2215
|
-
inputBeef.mergeBeef(sdk_1.Beef.fromBinary(token.tx));
|
|
2216
|
-
}
|
|
2217
|
-
const { signableTransaction } = await this.createAction({
|
|
2218
|
-
description: opts?.description ?? `Coalesce ${oldTokens.length} permission tokens`,
|
|
2219
|
-
inputBEEF: inputBeef.toBinary(),
|
|
2220
|
-
inputs: oldTokens.map((t, i) => ({
|
|
2221
|
-
outpoint: `${t.txid}.${t.outputIndex}`,
|
|
2222
|
-
unlockingScriptLength: 74,
|
|
2223
|
-
inputDescription: `Consume permission token #${i + 1}`
|
|
2224
|
-
})),
|
|
2225
|
-
outputs: [
|
|
2226
|
-
{
|
|
2227
|
-
lockingScript: newScript.toHex(),
|
|
2228
|
-
satoshis: 1,
|
|
2229
|
-
outputDescription: 'Renewed permission token',
|
|
2230
|
-
...(opts?.basket ? { basket: opts.basket } : {}),
|
|
2231
|
-
...((opts?.tags == null) ? {} : { tags: opts.tags })
|
|
2232
|
-
}
|
|
2233
|
-
],
|
|
2234
|
-
options: {
|
|
2235
|
-
acceptDelayedBroadcast: true,
|
|
2236
|
-
randomizeOutputs: false,
|
|
2237
|
-
signAndProcess: false
|
|
2238
|
-
}
|
|
2239
|
-
}, this.adminOriginator);
|
|
2240
|
-
if (!signableTransaction?.reference || !signableTransaction.tx) {
|
|
2241
|
-
throw new Error('Failed to create signable transaction');
|
|
2242
|
-
}
|
|
2243
|
-
// 2) Sign each input - each token needs its own unlocker with the correct locking script
|
|
2244
|
-
const partialTx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
|
|
2245
|
-
const pushdrop = new sdk_1.PushDrop(this.underlying);
|
|
2246
|
-
const spends = {};
|
|
2247
|
-
for (let i = 0; i < oldTokens.length; i++) {
|
|
2248
|
-
const token = oldTokens[i];
|
|
2249
|
-
// Each token requires its own unlocker with the specific locking script
|
|
2250
|
-
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
|
|
2251
|
-
const unlockingScript = await unlocker.sign(partialTx, i);
|
|
2252
|
-
spends[i] = { unlockingScript: unlockingScript.toHex() };
|
|
2253
|
-
}
|
|
2254
|
-
// 3) Finalize the action
|
|
2255
|
-
const { txid } = await this.underlying.signAction({
|
|
2256
|
-
reference: signableTransaction.reference,
|
|
2257
|
-
spends
|
|
2258
|
-
});
|
|
2259
|
-
if (!txid)
|
|
2260
|
-
throw new Error('Failed to finalize coalescing transaction');
|
|
2261
|
-
return txid;
|
|
2262
|
-
}
|
|
2263
|
-
/**
|
|
2264
|
-
* Renews a permission token by spending the old token as input and creating a new token output.
|
|
2265
|
-
* This invalidates the old token and replaces it with a new one.
|
|
2266
|
-
*
|
|
2267
|
-
* @param oldToken The old token to consume
|
|
2268
|
-
* @param r The permission request being renewed
|
|
2269
|
-
* @param newExpiry The new expiry epoch time
|
|
2270
|
-
* @param newAmount For DSAP, the new authorized amount
|
|
2271
|
-
*/
|
|
2272
|
-
async renewPermissionOnChain(oldToken, r, newExpiry, newAmount) {
|
|
2273
|
-
r.originator = this.normalizeOriginator(r.originator) || r.originator;
|
|
2274
|
-
// 1) build new fields
|
|
2275
|
-
const newFields = await this.buildPushdropFields(r, newExpiry, newAmount);
|
|
2276
|
-
// 2) new script
|
|
2277
|
-
const newScript = await new sdk_1.PushDrop(this.underlying).lock(newFields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
2278
|
-
const tags = this.buildTagsForRequest(r);
|
|
2279
|
-
// Check if there are multiple old tokens for the same parameters (shouldn't usually happen)
|
|
2280
|
-
const oldTokens = await this.findAllProtocolTokens(oldToken.originator, oldToken.privileged, [oldToken.securityLevel, oldToken.protocol], oldToken.counterparty, this.buildOriginatorLookupValues(oldToken.rawOriginator, oldToken.originator));
|
|
2281
|
-
// If so, coalesce them into a single token first, to avoid bloat
|
|
2282
|
-
if (oldTokens.length > 1) {
|
|
2283
|
-
await this.coalescePermissionTokens(oldTokens, newScript, {
|
|
2284
|
-
tags,
|
|
2285
|
-
basket: BASKET_MAP[r.type],
|
|
2286
|
-
description: `Coalesce ${r.type} permission tokens`
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
|
-
else {
|
|
2290
|
-
// Otherwise, just proceed with the single-token renewal
|
|
2291
|
-
// 3) For BRC-100, we do a "createAction" with a partial input referencing oldToken
|
|
2292
|
-
// plus a single new output. We'll hydrate the template, then signAction for the wallet to finalize.
|
|
2293
|
-
const oldOutpoint = `${oldToken.txid}.${oldToken.outputIndex}`;
|
|
2294
|
-
const { signableTransaction } = await this.createAction({
|
|
2295
|
-
description: `Renew ${r.type} permission`,
|
|
2296
|
-
inputBEEF: oldToken.tx,
|
|
2297
|
-
inputs: [
|
|
2298
|
-
{
|
|
2299
|
-
outpoint: oldOutpoint,
|
|
2300
|
-
unlockingScriptLength: 73, // length of signature
|
|
2301
|
-
inputDescription: `Consume old ${r.type} token`
|
|
2302
|
-
}
|
|
2303
|
-
],
|
|
2304
|
-
outputs: [
|
|
2305
|
-
{
|
|
2306
|
-
lockingScript: newScript.toHex(),
|
|
2307
|
-
satoshis: 1,
|
|
2308
|
-
outputDescription: `Renewed ${r.type} permission token`,
|
|
2309
|
-
basket: BASKET_MAP[r.type],
|
|
2310
|
-
tags
|
|
2311
|
-
}
|
|
2312
|
-
],
|
|
2313
|
-
options: {
|
|
2314
|
-
acceptDelayedBroadcast: true
|
|
2315
|
-
}
|
|
2316
|
-
}, this.adminOriginator);
|
|
2317
|
-
const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
|
|
2318
|
-
const unlocker = new sdk_1.PushDrop(this.underlying).unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(oldToken.outputScript));
|
|
2319
|
-
const unlockingScript = await unlocker.sign(tx, 0);
|
|
2320
|
-
await this.underlying.signAction({
|
|
2321
|
-
reference: signableTransaction.reference,
|
|
2322
|
-
spends: {
|
|
2323
|
-
0: {
|
|
2324
|
-
unlockingScript: unlockingScript.toHex()
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
/**
|
|
2331
|
-
* Builds the encrypted array of fields for a PushDrop permission token
|
|
2332
|
-
* (protocol / basket / certificate / spending).
|
|
2333
|
-
*/
|
|
2334
|
-
async buildPushdropFields(r, expiry, amount) {
|
|
2335
|
-
switch (r.type) {
|
|
2336
|
-
case 'protocol': {
|
|
2337
|
-
const [secLevel, protoName] = r.protocolID;
|
|
2338
|
-
const counterparty = secLevel === 2 ? (r.counterparty ?? 'self') : '';
|
|
2339
|
-
return [
|
|
2340
|
-
await this.encryptPermissionTokenField(r.originator), // domain
|
|
2341
|
-
await this.encryptPermissionTokenField(String(expiry)), // expiry
|
|
2342
|
-
await this.encryptPermissionTokenField(r.privileged === true ? 'true' : 'false'),
|
|
2343
|
-
await this.encryptPermissionTokenField(String(secLevel)),
|
|
2344
|
-
await this.encryptPermissionTokenField(protoName),
|
|
2345
|
-
await this.encryptPermissionTokenField(counterparty)
|
|
2346
|
-
];
|
|
2347
|
-
}
|
|
2348
|
-
case 'basket': {
|
|
2349
|
-
return [
|
|
2350
|
-
await this.encryptPermissionTokenField(r.originator),
|
|
2351
|
-
await this.encryptPermissionTokenField(String(expiry)),
|
|
2352
|
-
await this.encryptPermissionTokenField(r.basket)
|
|
2353
|
-
];
|
|
2354
|
-
}
|
|
2355
|
-
case 'certificate': {
|
|
2356
|
-
const { certType, fields, verifier } = r.certificate;
|
|
2357
|
-
return [
|
|
2358
|
-
await this.encryptPermissionTokenField(r.originator),
|
|
2359
|
-
await this.encryptPermissionTokenField(String(expiry)),
|
|
2360
|
-
await this.encryptPermissionTokenField(r.privileged ? 'true' : 'false'),
|
|
2361
|
-
await this.encryptPermissionTokenField(certType),
|
|
2362
|
-
await this.encryptPermissionTokenField(JSON.stringify(fields)),
|
|
2363
|
-
await this.encryptPermissionTokenField(verifier)
|
|
2364
|
-
];
|
|
2365
|
-
}
|
|
2366
|
-
case 'spending': {
|
|
2367
|
-
// DSAP
|
|
2368
|
-
const authAmt = amount ?? (r.spending?.satoshis || 0);
|
|
2369
|
-
return [
|
|
2370
|
-
await this.encryptPermissionTokenField(r.originator),
|
|
2371
|
-
await this.encryptPermissionTokenField(String(authAmt))
|
|
2372
|
-
];
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* Helper to build an array of tags for the new output, matching the user request's
|
|
2378
|
-
* origin, basket, privileged, protocol name, etc.
|
|
2379
|
-
*/
|
|
2380
|
-
buildTagsForRequest(r) {
|
|
2381
|
-
const tags = [`originator ${r.originator}`];
|
|
2382
|
-
switch (r.type) {
|
|
2383
|
-
case 'protocol': {
|
|
2384
|
-
tags.push(`privileged ${!!r.privileged}`, `protocolName ${r.protocolID[1]}`, `protocolSecurityLevel ${r.protocolID[0]}`);
|
|
2385
|
-
if (r.protocolID[0] === 2) {
|
|
2386
|
-
tags.push(`counterparty ${r.counterparty ?? 'self'}`);
|
|
2387
|
-
}
|
|
2388
|
-
break;
|
|
2389
|
-
}
|
|
2390
|
-
case 'basket': {
|
|
2391
|
-
tags.push(`basket ${r.basket}`);
|
|
2392
|
-
break;
|
|
2393
|
-
}
|
|
2394
|
-
case 'certificate': {
|
|
2395
|
-
tags.push(`privileged ${!!r.privileged}`, `type ${r.certificate.certType}`, `verifier ${r.certificate.verifier}`);
|
|
2396
|
-
break;
|
|
2397
|
-
}
|
|
2398
|
-
case 'spending': {
|
|
2399
|
-
// Only 'originator' is strictly required as a tag.
|
|
2400
|
-
break;
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
return tags;
|
|
2404
|
-
}
|
|
2405
|
-
/* ---------------------------------------------------------------------
|
|
2406
|
-
* 6) PUBLIC "LIST/HAS/REVOKE" METHODS
|
|
2407
|
-
* --------------------------------------------------------------------- */
|
|
2408
|
-
/**
|
|
2409
|
-
* Lists all protocol permission tokens (DPACP) with optional filters.
|
|
2410
|
-
* @param originator Optional originator domain to filter by
|
|
2411
|
-
* @param privileged Optional boolean to filter by privileged status
|
|
2412
|
-
* @param protocolName Optional protocol name to filter by
|
|
2413
|
-
* @param protocolSecurityLevel Optional protocol security level to filter by
|
|
2414
|
-
* @param counterparty Optional counterparty to filter by
|
|
2415
|
-
* @returns Array of permission tokens that match the filter criteria
|
|
2416
|
-
*/
|
|
2417
|
-
async listProtocolPermissions({ originator, privileged, protocolName, protocolSecurityLevel, counterparty } = {}) {
|
|
2418
|
-
const baseTags = this.buildProtocolFilterTags({ privileged, protocolName, protocolSecurityLevel, counterparty });
|
|
2419
|
-
const originFilter = originator ? this.prepareOriginator(originator) : undefined;
|
|
2420
|
-
const originVariants = (originFilter == null) ? [undefined] : originFilter.lookupValues;
|
|
2421
|
-
const seen = new Set();
|
|
2422
|
-
const tokens = [];
|
|
2423
|
-
for (const originTag of originVariants) {
|
|
2424
|
-
const tags = originTag != null ? [...baseTags, `originator ${originTag}`] : [...baseTags];
|
|
2425
|
-
const result = await this.underlying.listOutputs({ basket: BASKET_MAP.protocol, tags, tagQueryMode: 'all', include: 'entire transactions', limit: 10000 }, this.adminOriginator);
|
|
2426
|
-
await this.collectProtocolTokens(result, originFilter, seen, tokens);
|
|
2427
|
-
}
|
|
2428
|
-
return tokens;
|
|
2429
|
-
}
|
|
2430
|
-
/** Builds the base tag array for protocol permission listing. */
|
|
2431
|
-
buildProtocolFilterTags({ privileged, protocolName, protocolSecurityLevel, counterparty }) {
|
|
2432
|
-
const tags = [];
|
|
2433
|
-
if (privileged !== undefined)
|
|
2434
|
-
tags.push(`privileged ${!!privileged}`);
|
|
2435
|
-
if (protocolName)
|
|
2436
|
-
tags.push(`protocolName ${protocolName}`);
|
|
2437
|
-
if (protocolSecurityLevel !== undefined)
|
|
2438
|
-
tags.push(`protocolSecurityLevel ${protocolSecurityLevel}`);
|
|
2439
|
-
if (counterparty)
|
|
2440
|
-
tags.push(`counterparty ${counterparty}`);
|
|
2441
|
-
return tags;
|
|
2442
|
-
}
|
|
2443
|
-
/** Decodes and appends protocol permission tokens from a listOutputs result. */
|
|
2444
|
-
async collectProtocolTokens(result, originFilter, seen, tokens) {
|
|
2445
|
-
for (const out of result.outputs) {
|
|
2446
|
-
if (seen.has(out.outpoint))
|
|
2447
|
-
continue;
|
|
2448
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
2449
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
2450
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
2451
|
-
if (!dec?.fields || dec.fields.length < 6)
|
|
2452
|
-
continue;
|
|
2453
|
-
const f = await this.decryptProtocolTokenFields(dec.fields);
|
|
2454
|
-
const normalizedDomain = this.normalizeOriginator(f.domainDecoded);
|
|
2455
|
-
if ((originFilter != null) && normalizedDomain !== originFilter.normalized)
|
|
2456
|
-
continue;
|
|
2457
|
-
seen.add(out.outpoint);
|
|
2458
|
-
tokens.push({
|
|
2459
|
-
tx: tx.toBEEF(), txid, outputIndex,
|
|
2460
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
2461
|
-
satoshis: out.satoshis, originator: normalizedDomain, rawOriginator: f.domainDecoded,
|
|
2462
|
-
expiry: f.expiryDecoded, privileged: f.privDecoded,
|
|
2463
|
-
securityLevel: f.secLevelDecoded, protocol: f.protoNameDecoded, counterparty: f.cptyDecoded
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
/**
|
|
2468
|
-
* Returns true if the originator already holds a valid unexpired protocol permission.
|
|
2469
|
-
* This calls `ensureProtocolPermission` with `seekPermission=false`, so it won't prompt.
|
|
2470
|
-
*/
|
|
2471
|
-
async hasProtocolPermission(params) {
|
|
2472
|
-
try {
|
|
2473
|
-
await this.ensureProtocolPermission({
|
|
2474
|
-
...params,
|
|
2475
|
-
reason: 'hasProtocolPermission',
|
|
2476
|
-
seekPermission: false,
|
|
2477
|
-
usageType: 'generic'
|
|
2478
|
-
});
|
|
2479
|
-
return true;
|
|
2480
|
-
}
|
|
2481
|
-
catch {
|
|
2482
|
-
return false;
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
/**
|
|
2486
|
-
* Lists basket permission tokens (DBAP) for a given originator or basket (or for all if not specified).
|
|
2487
|
-
* @param params.originator Optional originator to filter by
|
|
2488
|
-
* @param params.basket Optional basket name to filter by
|
|
2489
|
-
* @returns Array of permission tokens that match the filter criteria
|
|
2490
|
-
*/
|
|
2491
|
-
async listBasketAccess(params = {}) {
|
|
2492
|
-
const basketName = BASKET_MAP.basket;
|
|
2493
|
-
const baseTags = [];
|
|
2494
|
-
if (params.basket) {
|
|
2495
|
-
baseTags.push(`basket ${params.basket}`);
|
|
2496
|
-
}
|
|
2497
|
-
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
2498
|
-
const originVariants = (originFilter == null) ? [undefined] : originFilter.lookupValues;
|
|
2499
|
-
const seen = new Set();
|
|
2500
|
-
const tokens = [];
|
|
2501
|
-
for (const originTag of originVariants) {
|
|
2502
|
-
const tags = [...baseTags];
|
|
2503
|
-
if (originTag) {
|
|
2504
|
-
tags.push(`originator ${originTag}`);
|
|
2505
|
-
}
|
|
2506
|
-
const result = await this.underlying.listOutputs({
|
|
2507
|
-
basket: basketName,
|
|
2508
|
-
tags,
|
|
2509
|
-
tagQueryMode: 'all',
|
|
2510
|
-
include: 'entire transactions',
|
|
2511
|
-
limit: 10000
|
|
2512
|
-
}, this.adminOriginator);
|
|
2513
|
-
for (const out of result.outputs) {
|
|
2514
|
-
if (seen.has(out.outpoint))
|
|
2515
|
-
continue;
|
|
2516
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
2517
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
2518
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
2519
|
-
if (!dec?.fields || dec.fields.length < 3)
|
|
2520
|
-
continue;
|
|
2521
|
-
const [domainRaw, expiryRaw, basketRaw] = dec.fields;
|
|
2522
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
2523
|
-
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
2524
|
-
if ((originFilter != null) && normalizedDomain !== originFilter.normalized)
|
|
2525
|
-
continue;
|
|
2526
|
-
const expiryDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
2527
|
-
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw));
|
|
2528
|
-
seen.add(out.outpoint);
|
|
2529
|
-
tokens.push({
|
|
2530
|
-
tx: tx.toBEEF(),
|
|
2531
|
-
txid,
|
|
2532
|
-
outputIndex,
|
|
2533
|
-
satoshis: out.satoshis,
|
|
2534
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
2535
|
-
originator: normalizedDomain,
|
|
2536
|
-
rawOriginator: domainDecoded,
|
|
2537
|
-
basketName: basketDecoded,
|
|
2538
|
-
expiry: expiryDecoded
|
|
2539
|
-
});
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
return tokens;
|
|
2543
|
-
}
|
|
2544
|
-
/**
|
|
2545
|
-
* Returns `true` if the originator already holds a valid unexpired basket permission for `basket`.
|
|
2546
|
-
*/
|
|
2547
|
-
async hasBasketAccess(params) {
|
|
2548
|
-
try {
|
|
2549
|
-
await this.ensureBasketAccess({
|
|
2550
|
-
originator: params.originator,
|
|
2551
|
-
basket: params.basket,
|
|
2552
|
-
seekPermission: false,
|
|
2553
|
-
usageType: 'insertion'
|
|
2554
|
-
});
|
|
2555
|
-
return true;
|
|
2556
|
-
}
|
|
2557
|
-
catch {
|
|
2558
|
-
return false;
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
/**
|
|
2562
|
-
* Lists spending authorization tokens (DSAP) for a given originator (or all).
|
|
2563
|
-
*/
|
|
2564
|
-
async listSpendingAuthorizations(params) {
|
|
2565
|
-
const basketName = BASKET_MAP.spending;
|
|
2566
|
-
const tags = [];
|
|
2567
|
-
if (params.originator) {
|
|
2568
|
-
tags.push(`originator ${params.originator}`);
|
|
2569
|
-
}
|
|
2570
|
-
const result = await this.underlying.listOutputs({
|
|
2571
|
-
basket: basketName,
|
|
2572
|
-
tags,
|
|
2573
|
-
tagQueryMode: 'all',
|
|
2574
|
-
include: 'entire transactions',
|
|
2575
|
-
limit: 10000
|
|
2576
|
-
}, this.adminOriginator);
|
|
2577
|
-
const tokens = [];
|
|
2578
|
-
for (const out of result.outputs) {
|
|
2579
|
-
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
2580
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
2581
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
2582
|
-
if (!dec?.fields || dec.fields.length < 2)
|
|
2583
|
-
continue;
|
|
2584
|
-
const [domainRaw, amtRaw] = dec.fields;
|
|
2585
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
2586
|
-
const amtDecodedStr = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw));
|
|
2587
|
-
const authorizedAmount = Number.parseInt(amtDecodedStr, 10);
|
|
2588
|
-
tokens.push({
|
|
2589
|
-
tx: tx.toBEEF(),
|
|
2590
|
-
txid: out.outpoint.split('.')[0],
|
|
2591
|
-
outputIndex: Number.parseInt(out.outpoint.split('.')[1], 10),
|
|
2592
|
-
satoshis: out.satoshis,
|
|
2593
|
-
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
2594
|
-
originator: domainDecoded,
|
|
2595
|
-
authorizedAmount,
|
|
2596
|
-
expiry: 0
|
|
2597
|
-
});
|
|
2598
|
-
}
|
|
2599
|
-
return tokens;
|
|
2600
|
-
}
|
|
2601
|
-
/**
|
|
2602
|
-
* Returns `true` if the originator already holds a valid spending authorization token
|
|
2603
|
-
* with enough available monthly spend. We do not prompt (seekPermission=false).
|
|
2604
|
-
*/
|
|
2605
|
-
async hasSpendingAuthorization(params) {
|
|
2606
|
-
try {
|
|
2607
|
-
await this.ensureSpendingAuthorization({
|
|
2608
|
-
originator: params.originator,
|
|
2609
|
-
satoshis: params.satoshis,
|
|
2610
|
-
seekPermission: false
|
|
2611
|
-
});
|
|
2612
|
-
return true;
|
|
2613
|
-
}
|
|
2614
|
-
catch {
|
|
2615
|
-
return false;
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
/**
|
|
2619
|
-
* Lists certificate permission tokens (DCAP) with optional filters.
|
|
2620
|
-
* @param originator Optional originator domain to filter by
|
|
2621
|
-
* @param privileged Optional boolean to filter by privileged status
|
|
2622
|
-
* @param certType Optional certificate type to filter by
|
|
2623
|
-
* @param verifier Optional verifier to filter by
|
|
2624
|
-
* @returns Array of permission tokens that match the filter criteria
|
|
2625
|
-
*/
|
|
2626
|
-
async listCertificateAccess(params = {}) {
|
|
2627
|
-
const baseTags = [];
|
|
2628
|
-
if (params.privileged !== undefined)
|
|
2629
|
-
baseTags.push(`privileged ${!!params.privileged}`);
|
|
2630
|
-
if (params.certType)
|
|
2631
|
-
baseTags.push(`type ${params.certType}`);
|
|
2632
|
-
if (params.verifier)
|
|
2633
|
-
baseTags.push(`verifier ${params.verifier}`);
|
|
2634
|
-
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
2635
|
-
const originVariants = (originFilter == null) ? [undefined] : originFilter.lookupValues;
|
|
2636
|
-
const seen = new Set();
|
|
2637
|
-
const tokens = [];
|
|
2638
|
-
for (const originTag of originVariants) {
|
|
2639
|
-
const tags = originTag != null ? [...baseTags, `originator ${originTag}`] : [...baseTags];
|
|
2640
|
-
const result = await this.underlying.listOutputs({ basket: BASKET_MAP.certificate, tags, tagQueryMode: 'all', include: 'entire transactions', limit: 10000 }, this.adminOriginator);
|
|
2641
|
-
await this.collectCertificateTokens(result, originFilter, seen, tokens);
|
|
2642
|
-
}
|
|
2643
|
-
return tokens;
|
|
2644
|
-
}
|
|
2645
|
-
/** Decodes and appends certificate permission tokens from a listOutputs result. */
|
|
2646
|
-
async collectCertificateTokens(result, originFilter, seen, tokens) {
|
|
2647
|
-
for (const out of result.outputs) {
|
|
2648
|
-
if (seen.has(out.outpoint))
|
|
2649
|
-
continue;
|
|
2650
|
-
const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
|
|
2651
|
-
const tx = this.transactionFromResultBeef(result, txid);
|
|
2652
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
2653
|
-
if (!dec?.fields || dec.fields.length < 6)
|
|
2654
|
-
continue;
|
|
2655
|
-
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
2656
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
2657
|
-
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
2658
|
-
if ((originFilter != null) && normalizedDomain !== originFilter.normalized)
|
|
2659
|
-
continue;
|
|
2660
|
-
const expiryDecoded = Number.parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
2661
|
-
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
2662
|
-
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
2663
|
-
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
2664
|
-
const allFields = JSON.parse(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(fieldsRaw)));
|
|
2665
|
-
seen.add(out.outpoint);
|
|
2666
|
-
tokens.push({
|
|
2667
|
-
tx: tx.toBEEF(), txid, outputIndex, satoshis: out.satoshis,
|
|
2668
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toHex(),
|
|
2669
|
-
originator: normalizedDomain, rawOriginator: domainDecoded,
|
|
2670
|
-
privileged: privDecoded, certType: typeDecoded, certFields: allFields,
|
|
2671
|
-
verifier: verifierDec, expiry: expiryDecoded
|
|
2672
|
-
});
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
/**
|
|
2676
|
-
* Returns `true` if the originator already holds a valid unexpired certificate access
|
|
2677
|
-
* for the given certType/fields. Does not prompt the user.
|
|
2678
|
-
*/
|
|
2679
|
-
async hasCertificateAccess(params) {
|
|
2680
|
-
try {
|
|
2681
|
-
await this.ensureCertificateAccess({
|
|
2682
|
-
originator: params.originator,
|
|
2683
|
-
privileged: params.privileged,
|
|
2684
|
-
verifier: params.verifier,
|
|
2685
|
-
certType: params.certType,
|
|
2686
|
-
fields: params.fields,
|
|
2687
|
-
seekPermission: false,
|
|
2688
|
-
usageType: 'disclosure'
|
|
2689
|
-
});
|
|
2690
|
-
return true;
|
|
2691
|
-
}
|
|
2692
|
-
catch {
|
|
2693
|
-
return false;
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
async revokePermissions(oldTokens) {
|
|
2697
|
-
return await this.revokePermissionTokensBestEffort(oldTokens);
|
|
2698
|
-
}
|
|
2699
|
-
async revokeAllForOriginator(originator, opts) {
|
|
2700
|
-
const preparedOriginator = this.prepareOriginator(originator);
|
|
2701
|
-
const include = {
|
|
2702
|
-
protocol: true,
|
|
2703
|
-
basket: true,
|
|
2704
|
-
certificate: true,
|
|
2705
|
-
spending: true,
|
|
2706
|
-
...opts
|
|
2707
|
-
};
|
|
2708
|
-
const [protocolTokens, basketTokens, certificateTokens, spendingTokens] = await Promise.all([
|
|
2709
|
-
include.protocol ? this.listProtocolPermissions({ originator }) : Promise.resolve([]),
|
|
2710
|
-
include.basket ? this.listBasketAccess({ originator }) : Promise.resolve([]),
|
|
2711
|
-
include.certificate ? this.listCertificateAccess({ originator }) : Promise.resolve([]),
|
|
2712
|
-
include.spending
|
|
2713
|
-
? this.listSpendingAuthorizations({ originator: preparedOriginator.normalized })
|
|
2714
|
-
: Promise.resolve([])
|
|
2715
|
-
]);
|
|
2716
|
-
const spendingTokenList = (spendingTokens.length > 0) ? [spendingTokens[0]] : [];
|
|
2717
|
-
const allTokens = [...protocolTokens, ...basketTokens, ...certificateTokens, ...spendingTokenList];
|
|
2718
|
-
const seen = new Set();
|
|
2719
|
-
const deduped = allTokens.filter(t => {
|
|
2720
|
-
const key = `${t.txid}.${t.outputIndex}`;
|
|
2721
|
-
if (seen.has(key))
|
|
2722
|
-
return false;
|
|
2723
|
-
seen.add(key);
|
|
2724
|
-
return true;
|
|
2725
|
-
});
|
|
2726
|
-
return await this.revokePermissions(deduped);
|
|
2727
|
-
}
|
|
2728
|
-
async revokePermissionTokensBestEffort(items) {
|
|
2729
|
-
const CHUNK = 15;
|
|
2730
|
-
return await this.runBestEffortBatches(items, CHUNK, async (chunk) => {
|
|
2731
|
-
await this.revokePermissionTokensChunk(chunk);
|
|
2732
|
-
return chunk;
|
|
2733
|
-
});
|
|
2734
|
-
}
|
|
2735
|
-
async revokePermissionTokensChunk(oldTokens) {
|
|
2736
|
-
if (oldTokens.length === 0)
|
|
2737
|
-
return;
|
|
2738
|
-
const inputBeef = new sdk_1.Beef();
|
|
2739
|
-
for (const token of oldTokens) {
|
|
2740
|
-
inputBeef.mergeBeef(sdk_1.Beef.fromBinary(token.tx));
|
|
2741
|
-
}
|
|
2742
|
-
const { signableTransaction } = await this.createAction({
|
|
2743
|
-
description: `Revoke ${oldTokens.length} permissions`,
|
|
2744
|
-
inputBEEF: inputBeef.toBinary(),
|
|
2745
|
-
inputs: oldTokens.map((t, i) => ({
|
|
2746
|
-
outpoint: `${t.txid}.${t.outputIndex}`,
|
|
2747
|
-
unlockingScriptLength: 73,
|
|
2748
|
-
inputDescription: `Consume old permission token #${i + 1}`
|
|
2749
|
-
})),
|
|
2750
|
-
options: {
|
|
2751
|
-
acceptDelayedBroadcast: true,
|
|
2752
|
-
randomizeOutputs: false,
|
|
2753
|
-
signAndProcess: false
|
|
2754
|
-
}
|
|
2755
|
-
}, this.adminOriginator);
|
|
2756
|
-
if (!signableTransaction?.reference || !signableTransaction.tx) {
|
|
2757
|
-
throw new Error('Failed to create signable transaction');
|
|
2758
|
-
}
|
|
2759
|
-
const tx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
|
|
2760
|
-
const inputsToSign = oldTokens.map(token => {
|
|
2761
|
-
let permInputIndex = this.findInputIndexForToken(tx, token);
|
|
2762
|
-
if (permInputIndex === -1 && tx.inputs.length === 1) {
|
|
2763
|
-
permInputIndex = 0;
|
|
2764
|
-
}
|
|
2765
|
-
if (permInputIndex === -1) {
|
|
2766
|
-
throw new Error('Unable to locate permission token input for revocation.');
|
|
2767
|
-
}
|
|
2768
|
-
return { token, permInputIndex };
|
|
2769
|
-
});
|
|
2770
|
-
const pushdrop = new sdk_1.PushDrop(this.underlying);
|
|
2771
|
-
const spends = {};
|
|
2772
|
-
const signed = await this.mapWithConcurrency(inputsToSign, 8, async ({ token, permInputIndex }) => {
|
|
2773
|
-
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
|
|
2774
|
-
const unlockingScript = await unlocker.sign(tx, permInputIndex);
|
|
2775
|
-
return { permInputIndex, unlockingScriptHex: unlockingScript.toHex() };
|
|
2776
|
-
});
|
|
2777
|
-
for (const s of signed) {
|
|
2778
|
-
spends[s.permInputIndex] = { unlockingScript: s.unlockingScriptHex };
|
|
2779
|
-
}
|
|
2780
|
-
const { txid } = await this.underlying.signAction({
|
|
2781
|
-
reference: signableTransaction.reference,
|
|
2782
|
-
spends
|
|
2783
|
-
});
|
|
2784
|
-
if (!txid)
|
|
2785
|
-
throw new Error('Failed to finalize revoke transaction');
|
|
2786
|
-
}
|
|
2787
|
-
/**
|
|
2788
|
-
* Revokes a permission token by spending it with no replacement output.
|
|
2789
|
-
* The manager builds a BRC-100 transaction that consumes the token, effectively invalidating it.
|
|
2790
|
-
*/
|
|
2791
|
-
async revokePermission(oldToken) {
|
|
2792
|
-
const oldOutpoint = `${oldToken.txid}.${oldToken.outputIndex}`;
|
|
2793
|
-
const { signableTransaction } = await this.createAction({
|
|
2794
|
-
description: 'Revoke permission',
|
|
2795
|
-
inputBEEF: oldToken.tx,
|
|
2796
|
-
inputs: [
|
|
2797
|
-
{
|
|
2798
|
-
outpoint: oldOutpoint,
|
|
2799
|
-
unlockingScriptLength: 73, // length of signature
|
|
2800
|
-
inputDescription: 'Consume old permission token'
|
|
2801
|
-
}
|
|
2802
|
-
],
|
|
2803
|
-
options: {
|
|
2804
|
-
acceptDelayedBroadcast: true
|
|
2805
|
-
}
|
|
2806
|
-
}, this.adminOriginator);
|
|
2807
|
-
const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
|
|
2808
|
-
let permInputIndex = this.findInputIndexForToken(tx, oldToken);
|
|
2809
|
-
if (permInputIndex === -1 && tx.inputs.length === 1) {
|
|
2810
|
-
permInputIndex = 0;
|
|
2811
|
-
}
|
|
2812
|
-
if (permInputIndex === -1) {
|
|
2813
|
-
throw new Error('Unable to locate permission token input for revocation.');
|
|
2814
|
-
}
|
|
2815
|
-
const unlocker = new sdk_1.PushDrop(this.underlying).unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(oldToken.outputScript));
|
|
2816
|
-
const unlockingScript = await unlocker.sign(tx, permInputIndex);
|
|
2817
|
-
await this.underlying.signAction({
|
|
2818
|
-
reference: signableTransaction.reference,
|
|
2819
|
-
spends: {
|
|
2820
|
-
[permInputIndex]: {
|
|
2821
|
-
unlockingScript: unlockingScript.toHex()
|
|
2822
|
-
}
|
|
2823
|
-
}
|
|
2824
|
-
});
|
|
2825
|
-
}
|
|
2826
|
-
/* ---------------------------------------------------------------------
|
|
2827
|
-
* 7) BRC-100 WALLET INTERFACE FORWARDING WITH PERMISSION CHECKS
|
|
2828
|
-
* --------------------------------------------------------------------- */
|
|
2829
|
-
async createAction(args, originator) {
|
|
2830
|
-
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
2831
|
-
const pModulesByScheme = new Map();
|
|
2832
|
-
const nonPBaskets = this.collectNonPBaskets(args.outputs, pModulesByScheme);
|
|
2833
|
-
const nonPLabels = this.splitLabelsByPermissionModule(args.labels, pModulesByScheme);
|
|
2834
|
-
// 2) Check permissions for non-P baskets
|
|
2835
|
-
for (const basket of nonPBaskets) {
|
|
2836
|
-
await this.ensureBasketAccess({ originator: originator, basket, reason: args.description, usageType: 'insertion' });
|
|
2837
|
-
}
|
|
2838
|
-
// 3) Check permissions for non-P labels
|
|
2839
|
-
for (const lbl of nonPLabels) {
|
|
2840
|
-
await this.ensureLabelAccess({ originator: originator, label: lbl, reason: args.description, usageType: 'apply' });
|
|
2841
|
-
}
|
|
2842
|
-
// 4) Force signAndProcess=false unless the originator is admin and explicitly sets it to true.
|
|
2843
|
-
const modifiedOptions = this.enforceSignAndProcess(args.options, originator);
|
|
2844
|
-
// 5) Encrypt transaction metadata, saving originals for spending auth and line items.
|
|
2845
|
-
const originalDescription = args.description;
|
|
2846
|
-
const { originalInputDescriptions, originalOutputDescriptions } = await this.encryptActionMetadata(args);
|
|
2847
|
-
// 6) Call the underlying wallet's createAction with P-module chaining as needed.
|
|
2848
|
-
const finalArgs = {
|
|
2849
|
-
...args,
|
|
2850
|
-
options: modifiedOptions,
|
|
2851
|
-
labels: [...(args.labels || []), `admin originator ${originator}`, `admin month ${this.getCurrentMonthYearUTC()}`]
|
|
2852
|
-
};
|
|
2853
|
-
let createResult = await this.callCreateActionWithPModules(finalArgs, pModulesByScheme, originator);
|
|
2854
|
-
if (createResult.signableTransaction == null)
|
|
2855
|
-
return createResult;
|
|
2856
|
-
// 7) Parse the signable tx to determine net spend, then gate on spending authorization.
|
|
2857
|
-
const tx = sdk_1.Transaction.fromAtomicBEEF(createResult.signableTransaction.tx);
|
|
2858
|
-
const reference = createResult.signableTransaction.reference;
|
|
2859
|
-
// 7a) SECURITY (GHSA-36f9-7rg5-cpf8) defense-in-depth: confirm every
|
|
2860
|
-
// caller-requested output actually appears in the transaction we are about to
|
|
2861
|
-
// authorize and sign. The locking scripts in the signable transaction
|
|
2862
|
-
// originate from storage; a malicious or compromised remote storage provider
|
|
2863
|
-
// could substitute a recipient script, which would then be signed and
|
|
2864
|
-
// broadcast while this manager's caller/UI still shows the requested
|
|
2865
|
-
// recipient. The signer (buildSignableTransaction) rejects this at the
|
|
2866
|
-
// source; this is an independent check at the permissions layer.
|
|
2867
|
-
try {
|
|
2868
|
-
this.verifyRequestedOutputsPresent(tx, args);
|
|
2869
|
-
}
|
|
2870
|
-
catch (err) {
|
|
2871
|
-
await this.underlying.abortAction({ reference });
|
|
2872
|
-
throw err;
|
|
2873
|
-
}
|
|
2874
|
-
const { netSpent, lineItems } = this.computeNetSpend(tx, args, originalInputDescriptions, originalOutputDescriptions);
|
|
2875
|
-
// 8) If netSpent > 0, require spending authorization. Abort if denied.
|
|
2876
|
-
if (netSpent > 0) {
|
|
2877
|
-
try {
|
|
2878
|
-
await this.ensureSpendingAuthorization({ originator: originator, satoshis: netSpent, lineItems, reason: originalDescription });
|
|
2879
|
-
}
|
|
2880
|
-
catch (err) {
|
|
2881
|
-
await this.underlying.abortAction({ reference });
|
|
2882
|
-
throw err;
|
|
2883
|
-
}
|
|
2884
|
-
}
|
|
2885
|
-
// 9) Finalize or return the signable transaction based on whether more signatures are needed.
|
|
2886
|
-
const vargs = sdk_1.Validation.validateCreateActionArgs(args);
|
|
2887
|
-
if (vargs.isSignAction)
|
|
2888
|
-
return createResult;
|
|
2889
|
-
const signResult = await this.underlying.signAction({ reference, spends: {}, options: args.options }, originator);
|
|
2890
|
-
return { ...createResult, ...signResult, signableTransaction: undefined };
|
|
2891
|
-
}
|
|
2892
|
-
/** Scans outputs to split P-scheme baskets from regular baskets; registers P-modules as a side effect. */
|
|
2893
|
-
collectNonPBaskets(outputs, pModulesByScheme) {
|
|
2894
|
-
const nonPBaskets = [];
|
|
2895
|
-
if (outputs == null)
|
|
2896
|
-
return nonPBaskets;
|
|
2897
|
-
for (const out of outputs) {
|
|
2898
|
-
if (!out.basket)
|
|
2899
|
-
continue;
|
|
2900
|
-
if (out.basket.startsWith('p ')) {
|
|
2901
|
-
this.addPModuleByScheme(out.basket.split(' ')[1], 'basket', pModulesByScheme);
|
|
2902
|
-
}
|
|
2903
|
-
else {
|
|
2904
|
-
nonPBaskets.push(out.basket);
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
return nonPBaskets;
|
|
2908
|
-
}
|
|
2909
|
-
/** Enforces signAndProcess=false for non-admin originators; throws if admin override is missing. */
|
|
2910
|
-
enforceSignAndProcess(options, originator) {
|
|
2911
|
-
const modifiedOptions = { ...options };
|
|
2912
|
-
if (modifiedOptions.signAndProcess !== true) {
|
|
2913
|
-
modifiedOptions.signAndProcess = false;
|
|
2914
|
-
}
|
|
2915
|
-
else if (!this.isAdminOriginator(originator)) {
|
|
2916
|
-
throw new Error('Only the admin originator can set signAndProcess=true explicitly.');
|
|
2917
|
-
}
|
|
2918
|
-
return modifiedOptions;
|
|
2919
|
-
}
|
|
2920
|
-
/** Encrypts all description/instruction fields in args in-place; returns original (plaintext) copies. */
|
|
2921
|
-
async encryptActionMetadata(args) {
|
|
2922
|
-
const originalInputDescriptions = {};
|
|
2923
|
-
const originalOutputDescriptions = {};
|
|
2924
|
-
const inputEncryptionTasks = (args.inputs || []).map(async (input, i) => {
|
|
2925
|
-
if (!input.inputDescription)
|
|
2926
|
-
return;
|
|
2927
|
-
originalInputDescriptions[i] = input.inputDescription;
|
|
2928
|
-
input.inputDescription = await this.maybeEncryptMetadata(input.inputDescription);
|
|
2929
|
-
});
|
|
2930
|
-
const outputEncryptionTasks = (args.outputs || []).map(async (output, i) => {
|
|
2931
|
-
if (output.outputDescription) {
|
|
2932
|
-
originalOutputDescriptions[i] = output.outputDescription;
|
|
2933
|
-
output.outputDescription = await this.maybeEncryptMetadata(output.outputDescription);
|
|
2934
|
-
}
|
|
2935
|
-
if (output.customInstructions) {
|
|
2936
|
-
output.customInstructions = await this.maybeEncryptMetadata(output.customInstructions);
|
|
2937
|
-
}
|
|
2938
|
-
});
|
|
2939
|
-
await Promise.all([
|
|
2940
|
-
(async () => { args.description = await this.maybeEncryptMetadata(args.description); })(),
|
|
2941
|
-
...inputEncryptionTasks,
|
|
2942
|
-
...outputEncryptionTasks
|
|
2943
|
-
]);
|
|
2944
|
-
return { originalInputDescriptions, originalOutputDescriptions };
|
|
2945
|
-
}
|
|
2946
|
-
/** Calls underlying createAction, chaining P-module request/response transforms when needed. */
|
|
2947
|
-
async callCreateActionWithPModules(finalArgs, pModulesByScheme, originator) {
|
|
2948
|
-
if (pModulesByScheme.size === 0)
|
|
2949
|
-
return await this.underlying.createAction(finalArgs, originator);
|
|
2950
|
-
const pModules = Array.from(pModulesByScheme.values());
|
|
2951
|
-
let transformedArgs = finalArgs;
|
|
2952
|
-
for (const module of pModules) {
|
|
2953
|
-
const transformed = await module.onRequest({ method: 'createAction', args: transformedArgs, originator: originator });
|
|
2954
|
-
transformedArgs = transformed.args;
|
|
2955
|
-
}
|
|
2956
|
-
let createResult = await this.underlying.createAction(transformedArgs, originator);
|
|
2957
|
-
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
2958
|
-
createResult = await pModules[i].onResponse(createResult, { method: 'createAction', originator: originator });
|
|
2959
|
-
}
|
|
2960
|
-
return createResult;
|
|
2961
|
-
}
|
|
2962
|
-
/**
|
|
2963
|
-
* Computes the net satoshis the originator is spending in a createAction call,
|
|
2964
|
-
* accounting for foreign (originator-provided) inputs and outputs plus the fee.
|
|
2965
|
-
* Also builds the line items list for the spending authorization request.
|
|
2966
|
-
*/
|
|
2967
|
-
/**
|
|
2968
|
-
* Defense-in-depth verification that each caller-requested output (locking
|
|
2969
|
-
* script + amount) is present in the transaction returned for signing.
|
|
2970
|
-
*
|
|
2971
|
-
* The scripts in the signable transaction originate from storage. A malicious
|
|
2972
|
-
* or compromised remote storage provider could substitute a different
|
|
2973
|
-
* recipient script for a caller-specified output. The signer
|
|
2974
|
-
* (buildSignableTransaction) rejects this at the source; this is an
|
|
2975
|
-
* independent check at the permissions layer so the substitution is caught
|
|
2976
|
-
* regardless of how the signable transaction was produced.
|
|
2977
|
-
*
|
|
2978
|
-
* Outputs are matched as a multiset on (lockingScript, satoshis): the
|
|
2979
|
-
* transaction also contains change/commission outputs and the output order is
|
|
2980
|
-
* randomized by default, and a caller may legitimately request the same
|
|
2981
|
-
* script+amount more than once.
|
|
2982
|
-
*
|
|
2983
|
-
* @throws Error if any caller-requested output is absent from the transaction.
|
|
2984
|
-
*/
|
|
2985
|
-
verifyRequestedOutputsPresent(tx, args) {
|
|
2986
|
-
const requested = args.outputs || [];
|
|
2987
|
-
if (requested.length === 0)
|
|
2988
|
-
return;
|
|
2989
|
-
// All transaction outputs as (script hex, satoshis); each may satisfy at
|
|
2990
|
-
// most one requested output.
|
|
2991
|
-
const available = tx.outputs.map(o => ({
|
|
2992
|
-
script: o.lockingScript.toHex().toLowerCase(),
|
|
2993
|
-
satoshis: o.satoshis,
|
|
2994
|
-
used: false
|
|
2995
|
-
}));
|
|
2996
|
-
for (let i = 0; i < requested.length; i++) {
|
|
2997
|
-
const wantScript = (requested[i].lockingScript ?? '').toLowerCase();
|
|
2998
|
-
const wantSats = requested[i].satoshis;
|
|
2999
|
-
const match = available.find(a => !a.used && a.script === wantScript && a.satoshis === wantSats);
|
|
3000
|
-
if (match == null) {
|
|
3001
|
-
throw new Error(`The transaction returned for signing does not contain caller-requested output ${i} ` +
|
|
3002
|
-
`(locking script and amount). The recipient may have been substituted by storage.`);
|
|
3003
|
-
}
|
|
3004
|
-
match.used = true;
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
computeNetSpend(tx, args, originalInputDescriptions, originalOutputDescriptions) {
|
|
3008
|
-
const lineItems = [];
|
|
3009
|
-
// Sum originator-provided inputs:
|
|
3010
|
-
let totalInputSatoshis = 0;
|
|
3011
|
-
for (const input of tx.inputs) {
|
|
3012
|
-
const outpoint = `${input.sourceTXID}.${input.sourceOutputIndex}`;
|
|
3013
|
-
const matchingIndex = (args.inputs || []).findIndex(i => i.outpoint === outpoint);
|
|
3014
|
-
if (matchingIndex !== -1) {
|
|
3015
|
-
const satoshis = input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis;
|
|
3016
|
-
totalInputSatoshis += satoshis;
|
|
3017
|
-
lineItems.push({
|
|
3018
|
-
type: 'input',
|
|
3019
|
-
description: originalInputDescriptions[matchingIndex] || 'No input description provided',
|
|
3020
|
-
satoshis
|
|
3021
|
-
});
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
// Sum originator-requested outputs:
|
|
3025
|
-
const totalOutputSatoshis = (args.outputs || []).reduce((acc, out) => acc + out.satoshis, 0);
|
|
3026
|
-
for (const outIndex in args.outputs || []) {
|
|
3027
|
-
const out = args.outputs[outIndex];
|
|
3028
|
-
lineItems.push({
|
|
3029
|
-
type: 'output',
|
|
3030
|
-
satoshis: out.satoshis,
|
|
3031
|
-
description: originalOutputDescriptions[outIndex] || 'No output description provided'
|
|
3032
|
-
});
|
|
3033
|
-
}
|
|
3034
|
-
// Add an entry for the transaction fee:
|
|
3035
|
-
const fee = tx.getFee();
|
|
3036
|
-
lineItems.push({ type: 'fee', satoshis: fee, description: 'Network fee' });
|
|
3037
|
-
/**
|
|
3038
|
-
* Net spend = total foreign outflows minus total foreign inflows plus fee.
|
|
3039
|
-
* Domestic wallet inputs/change outputs are not counted — they are internal.
|
|
3040
|
-
*/
|
|
3041
|
-
const netSpent = totalOutputSatoshis + fee - totalInputSatoshis;
|
|
3042
|
-
return { netSpent, lineItems };
|
|
3043
|
-
}
|
|
3044
|
-
async signAction(...args) {
|
|
3045
|
-
return await this.underlying.signAction(...args);
|
|
3046
|
-
}
|
|
3047
|
-
async abortAction(...args) {
|
|
3048
|
-
return await this.underlying.abortAction(...args);
|
|
3049
|
-
}
|
|
3050
|
-
async listActions(...args) {
|
|
3051
|
-
const [requestArgs, originator] = args;
|
|
3052
|
-
const { remainingLabels: labelsForPermissionChecks } = (0, brc114ActionTimeLabels_1.parseBrc114ActionTimeLabels)(requestArgs.labels);
|
|
3053
|
-
// 1) Identify unique P-modules involved (one per schemeID, preserving label order)
|
|
3054
|
-
const pModulesByScheme = new Map();
|
|
3055
|
-
const nonPLabels = this.splitLabelsByPermissionModule(labelsForPermissionChecks, pModulesByScheme);
|
|
3056
|
-
// 2) Check permissions for non-P labels
|
|
3057
|
-
for (const lbl of nonPLabels) {
|
|
3058
|
-
await this.ensureLabelAccess({
|
|
3059
|
-
originator: originator,
|
|
3060
|
-
label: lbl,
|
|
3061
|
-
reason: 'listActions',
|
|
3062
|
-
usageType: 'list'
|
|
3063
|
-
});
|
|
3064
|
-
}
|
|
3065
|
-
// 3) Call underlying wallet, with P-module transformations if needed
|
|
3066
|
-
let results;
|
|
3067
|
-
if (pModulesByScheme.size > 0) {
|
|
3068
|
-
// P-modules are involved - chain transformations
|
|
3069
|
-
const pModules = Array.from(pModulesByScheme.values());
|
|
3070
|
-
// Chain onRequest calls through all modules in order
|
|
3071
|
-
let transformedArgs = requestArgs;
|
|
3072
|
-
for (const module of pModules) {
|
|
3073
|
-
const transformed = await module.onRequest({
|
|
3074
|
-
method: 'listActions',
|
|
3075
|
-
args: transformedArgs,
|
|
3076
|
-
originator: originator
|
|
3077
|
-
});
|
|
3078
|
-
transformedArgs = transformed.args;
|
|
3079
|
-
}
|
|
3080
|
-
// Call underlying wallet with transformed args
|
|
3081
|
-
results = await this.underlying.listActions(transformedArgs, originator);
|
|
3082
|
-
// Chain onResponse calls in reverse order
|
|
3083
|
-
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
3084
|
-
results = await pModules[i].onResponse(results, {
|
|
3085
|
-
method: 'listActions',
|
|
3086
|
-
originator: originator
|
|
3087
|
-
});
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
else {
|
|
3091
|
-
// No P-modules - call underlying wallet directly
|
|
3092
|
-
results = await this.underlying.listActions(...args);
|
|
3093
|
-
}
|
|
3094
|
-
// 4) Transparently decrypt transaction metadata, if configured to do so.
|
|
3095
|
-
return await this.decryptListActionsMetadata(results);
|
|
3096
|
-
}
|
|
3097
|
-
async internalizeAction(...args) {
|
|
3098
|
-
const [requestArgs, originator] = args;
|
|
3099
|
-
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
3100
|
-
const pModulesByScheme = new Map();
|
|
3101
|
-
const nonPBaskets = [];
|
|
3102
|
-
// Check baskets for p modules
|
|
3103
|
-
for (const outIndex in requestArgs.outputs) {
|
|
3104
|
-
const out = requestArgs.outputs[outIndex];
|
|
3105
|
-
if (out.protocol === 'basket insertion') {
|
|
3106
|
-
const basket = out.insertionRemittance.basket;
|
|
3107
|
-
if (basket.startsWith('p ')) {
|
|
3108
|
-
const schemeID = basket.split(' ')[1];
|
|
3109
|
-
this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme);
|
|
3110
|
-
}
|
|
3111
|
-
else {
|
|
3112
|
-
// Track non-P baskets for normal permission checks
|
|
3113
|
-
nonPBaskets.push({
|
|
3114
|
-
outIndex,
|
|
3115
|
-
basket,
|
|
3116
|
-
customInstructions: out.insertionRemittance.customInstructions
|
|
3117
|
-
});
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
// Check labels for p modules
|
|
3122
|
-
const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme);
|
|
3123
|
-
// 2) Check permissions for non-P baskets
|
|
3124
|
-
for (const { outIndex, basket, customInstructions } of nonPBaskets) {
|
|
3125
|
-
await this.ensureBasketAccess({
|
|
3126
|
-
originator: originator,
|
|
3127
|
-
basket,
|
|
3128
|
-
reason: requestArgs.description,
|
|
3129
|
-
usageType: 'insertion'
|
|
3130
|
-
});
|
|
3131
|
-
if (customInstructions) {
|
|
3132
|
-
requestArgs.outputs[outIndex].insertionRemittance.customInstructions =
|
|
3133
|
-
await this.maybeEncryptMetadata(customInstructions);
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
3136
|
-
// 3) Check permissions for non-P labels
|
|
3137
|
-
for (const lbl of nonPLabels) {
|
|
3138
|
-
await this.ensureLabelAccess({
|
|
3139
|
-
originator: originator,
|
|
3140
|
-
label: lbl,
|
|
3141
|
-
reason: requestArgs.description,
|
|
3142
|
-
usageType: 'apply'
|
|
3143
|
-
});
|
|
3144
|
-
}
|
|
3145
|
-
// 4) Call underlying wallet, with P-module transformations if needed
|
|
3146
|
-
if (pModulesByScheme.size > 0) {
|
|
3147
|
-
// P-modules are involved - chain transformations
|
|
3148
|
-
const pModules = Array.from(pModulesByScheme.values());
|
|
3149
|
-
// Chain onRequest calls through all modules in order
|
|
3150
|
-
let transformedArgs = requestArgs;
|
|
3151
|
-
for (const module of pModules) {
|
|
3152
|
-
const transformed = await module.onRequest({
|
|
3153
|
-
method: 'internalizeAction',
|
|
3154
|
-
args: transformedArgs,
|
|
3155
|
-
originator: originator
|
|
3156
|
-
});
|
|
3157
|
-
transformedArgs = transformed.args;
|
|
3158
|
-
}
|
|
3159
|
-
// Encrypt custom instructions for p basket outputs
|
|
3160
|
-
for (const outIndex in transformedArgs.outputs) {
|
|
3161
|
-
const out = transformedArgs.outputs[outIndex];
|
|
3162
|
-
if (out.protocol === 'basket insertion' && out.insertionRemittance?.customInstructions) {
|
|
3163
|
-
out.insertionRemittance.customInstructions = await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
// Call underlying wallet with transformed args
|
|
3167
|
-
let results = await this.underlying.internalizeAction(transformedArgs, originator);
|
|
3168
|
-
// Chain onResponse calls in reverse order
|
|
3169
|
-
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
3170
|
-
results = await pModules[i].onResponse(results, {
|
|
3171
|
-
method: 'internalizeAction',
|
|
3172
|
-
originator: originator
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
return results;
|
|
3176
|
-
}
|
|
3177
|
-
// No P-modules - call underlying wallet directly
|
|
3178
|
-
return await this.underlying.internalizeAction(...args);
|
|
3179
|
-
}
|
|
3180
|
-
async listOutputs(...args) {
|
|
3181
|
-
const [requestArgs, originator] = args;
|
|
3182
|
-
// Delegate to permission module if needed
|
|
3183
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.basket, 'listOutputs', requestArgs, originator, async (transformedArgs) => {
|
|
3184
|
-
const result = await this.underlying.listOutputs(transformedArgs, originator);
|
|
3185
|
-
// Apply metadata decryption to permission module response
|
|
3186
|
-
return await this.decryptListOutputsMetadata(result);
|
|
3187
|
-
});
|
|
3188
|
-
if (pModuleResult !== null) {
|
|
3189
|
-
return pModuleResult;
|
|
3190
|
-
}
|
|
3191
|
-
// Ensure the originator has permission for the basket.
|
|
3192
|
-
await this.ensureBasketAccess({
|
|
3193
|
-
originator: originator,
|
|
3194
|
-
basket: requestArgs.basket,
|
|
3195
|
-
reason: 'listOutputs',
|
|
3196
|
-
usageType: 'listing'
|
|
3197
|
-
});
|
|
3198
|
-
const results = await this.underlying.listOutputs(...args);
|
|
3199
|
-
// Apply metadata decryption to regular response
|
|
3200
|
-
return await this.decryptListOutputsMetadata(results);
|
|
3201
|
-
}
|
|
3202
|
-
async relinquishOutput(...args) {
|
|
3203
|
-
const [requestArgs, originator] = args;
|
|
3204
|
-
// Delegate to permission module if needed
|
|
3205
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.basket, 'relinquishOutput', requestArgs, originator, async (transformedArgs) => {
|
|
3206
|
-
return await this.underlying.relinquishOutput(transformedArgs, originator);
|
|
3207
|
-
});
|
|
3208
|
-
if (pModuleResult !== null) {
|
|
3209
|
-
return pModuleResult;
|
|
3210
|
-
}
|
|
3211
|
-
await this.ensureBasketAccess({
|
|
3212
|
-
originator: originator,
|
|
3213
|
-
basket: requestArgs.basket,
|
|
3214
|
-
reason: 'relinquishOutput',
|
|
3215
|
-
usageType: 'removal'
|
|
3216
|
-
});
|
|
3217
|
-
return await this.underlying.relinquishOutput(...args);
|
|
3218
|
-
}
|
|
3219
|
-
async getPublicKey(...args) {
|
|
3220
|
-
const [requestArgs, originator] = args;
|
|
3221
|
-
if (requestArgs.protocolID != null) {
|
|
3222
|
-
// Delegate to permission module if needed
|
|
3223
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'getPublicKey', requestArgs, originator, async (transformedArgs) => {
|
|
3224
|
-
return await this.underlying.getPublicKey(transformedArgs, originator);
|
|
3225
|
-
});
|
|
3226
|
-
if (pModuleResult !== null) {
|
|
3227
|
-
return pModuleResult;
|
|
3228
|
-
}
|
|
3229
|
-
// Not a P-protocol, continue with normal permission flow
|
|
3230
|
-
await this.ensureProtocolPermission({
|
|
3231
|
-
originator: originator,
|
|
3232
|
-
privileged: requestArgs.privileged,
|
|
3233
|
-
protocolID: requestArgs.protocolID,
|
|
3234
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3235
|
-
reason: requestArgs.privilegedReason,
|
|
3236
|
-
seekPermission: requestArgs.seekPermission,
|
|
3237
|
-
usageType: 'publicKey'
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
if (requestArgs.identityKey) {
|
|
3241
|
-
// We also require a minimal protocol permission to retrieve the user's identity key
|
|
3242
|
-
await this.ensureProtocolPermission({
|
|
3243
|
-
originator: originator,
|
|
3244
|
-
privileged: requestArgs.privileged,
|
|
3245
|
-
protocolID: [1, 'identity key retrieval'],
|
|
3246
|
-
counterparty: 'self',
|
|
3247
|
-
reason: requestArgs.privilegedReason,
|
|
3248
|
-
seekPermission: requestArgs.seekPermission,
|
|
3249
|
-
usageType: 'identityKey'
|
|
3250
|
-
});
|
|
3251
|
-
}
|
|
3252
|
-
return await this.underlying.getPublicKey(...args);
|
|
3253
|
-
}
|
|
3254
|
-
async revealCounterpartyKeyLinkage(...args) {
|
|
3255
|
-
const [requestArgs, originator] = args;
|
|
3256
|
-
await this.ensureProtocolPermission({
|
|
3257
|
-
originator: originator,
|
|
3258
|
-
privileged: requestArgs.privileged,
|
|
3259
|
-
protocolID: [2, `counterparty key linkage revelation ${requestArgs.counterparty}`],
|
|
3260
|
-
counterparty: requestArgs.verifier,
|
|
3261
|
-
reason: requestArgs.privilegedReason,
|
|
3262
|
-
usageType: 'linkageRevelation'
|
|
3263
|
-
});
|
|
3264
|
-
return await this.underlying.revealCounterpartyKeyLinkage(...args);
|
|
3265
|
-
}
|
|
3266
|
-
async revealSpecificKeyLinkage(...args) {
|
|
3267
|
-
const [requestArgs, originator] = args;
|
|
3268
|
-
await this.ensureProtocolPermission({
|
|
3269
|
-
originator: originator,
|
|
3270
|
-
privileged: requestArgs.privileged,
|
|
3271
|
-
protocolID: [
|
|
3272
|
-
2,
|
|
3273
|
-
`specific key linkage revelation ${requestArgs.protocolID[1]} ${requestArgs.protocolID[0] === 2 ? requestArgs.keyID : 'all'}`
|
|
3274
|
-
],
|
|
3275
|
-
counterparty: requestArgs.verifier,
|
|
3276
|
-
reason: requestArgs.privilegedReason,
|
|
3277
|
-
usageType: 'linkageRevelation'
|
|
3278
|
-
});
|
|
3279
|
-
return await this.underlying.revealSpecificKeyLinkage(...args);
|
|
3280
|
-
}
|
|
3281
|
-
async encrypt(...args) {
|
|
3282
|
-
const [requestArgs, originator] = args;
|
|
3283
|
-
// Delegate to permission module if needed
|
|
3284
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'encrypt', requestArgs, originator, async (transformedArgs) => {
|
|
3285
|
-
return await this.underlying.encrypt(transformedArgs, originator);
|
|
3286
|
-
});
|
|
3287
|
-
if (pModuleResult !== null) {
|
|
3288
|
-
return pModuleResult;
|
|
3289
|
-
}
|
|
3290
|
-
await this.ensureProtocolPermission({
|
|
3291
|
-
originator: originator,
|
|
3292
|
-
protocolID: requestArgs.protocolID,
|
|
3293
|
-
privileged: requestArgs.privileged,
|
|
3294
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3295
|
-
reason: requestArgs.privilegedReason,
|
|
3296
|
-
usageType: 'encrypting'
|
|
3297
|
-
});
|
|
3298
|
-
return await this.underlying.encrypt(...args);
|
|
3299
|
-
}
|
|
3300
|
-
async decrypt(...args) {
|
|
3301
|
-
const [requestArgs, originator] = args;
|
|
3302
|
-
// Delegate to permission module if needed
|
|
3303
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'decrypt', requestArgs, originator, async (transformedArgs) => {
|
|
3304
|
-
return await this.underlying.decrypt(transformedArgs, originator);
|
|
3305
|
-
});
|
|
3306
|
-
if (pModuleResult !== null) {
|
|
3307
|
-
return pModuleResult;
|
|
3308
|
-
}
|
|
3309
|
-
await this.ensureProtocolPermission({
|
|
3310
|
-
originator: originator,
|
|
3311
|
-
privileged: requestArgs.privileged,
|
|
3312
|
-
protocolID: requestArgs.protocolID,
|
|
3313
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3314
|
-
reason: requestArgs.privilegedReason,
|
|
3315
|
-
usageType: 'encrypting'
|
|
3316
|
-
});
|
|
3317
|
-
return await this.underlying.decrypt(...args);
|
|
3318
|
-
}
|
|
3319
|
-
async createHmac(...args) {
|
|
3320
|
-
const [requestArgs, originator] = args;
|
|
3321
|
-
// Delegate to permission module if needed
|
|
3322
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'createHmac', requestArgs, originator, async (transformedArgs) => {
|
|
3323
|
-
return await this.underlying.createHmac(transformedArgs, originator);
|
|
3324
|
-
});
|
|
3325
|
-
if (pModuleResult !== null) {
|
|
3326
|
-
return pModuleResult;
|
|
3327
|
-
}
|
|
3328
|
-
await this.ensureProtocolPermission({
|
|
3329
|
-
originator: originator,
|
|
3330
|
-
privileged: requestArgs.privileged,
|
|
3331
|
-
protocolID: requestArgs.protocolID,
|
|
3332
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3333
|
-
reason: requestArgs.privilegedReason,
|
|
3334
|
-
usageType: 'hmac'
|
|
3335
|
-
});
|
|
3336
|
-
return await this.underlying.createHmac(...args);
|
|
3337
|
-
}
|
|
3338
|
-
async verifyHmac(...args) {
|
|
3339
|
-
const [requestArgs, originator] = args;
|
|
3340
|
-
// Delegate to permission module if needed
|
|
3341
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'verifyHmac', requestArgs, originator, async (transformedArgs) => {
|
|
3342
|
-
return await this.underlying.verifyHmac(transformedArgs, originator);
|
|
3343
|
-
});
|
|
3344
|
-
if (pModuleResult !== null) {
|
|
3345
|
-
return pModuleResult;
|
|
3346
|
-
}
|
|
3347
|
-
await this.ensureProtocolPermission({
|
|
3348
|
-
originator: originator,
|
|
3349
|
-
privileged: requestArgs.privileged,
|
|
3350
|
-
protocolID: requestArgs.protocolID,
|
|
3351
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3352
|
-
reason: requestArgs.privilegedReason,
|
|
3353
|
-
usageType: 'hmac'
|
|
3354
|
-
});
|
|
3355
|
-
return await this.underlying.verifyHmac(...args);
|
|
3356
|
-
}
|
|
3357
|
-
async createSignature(...args) {
|
|
3358
|
-
const [requestArgs, originator] = args;
|
|
3359
|
-
// Delegate to permission module if needed
|
|
3360
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'createSignature', requestArgs, originator, async (transformedArgs) => {
|
|
3361
|
-
return await this.underlying.createSignature(transformedArgs, originator);
|
|
3362
|
-
});
|
|
3363
|
-
if (pModuleResult !== null) {
|
|
3364
|
-
return pModuleResult;
|
|
3365
|
-
}
|
|
3366
|
-
await this.ensureProtocolPermission({
|
|
3367
|
-
originator: originator,
|
|
3368
|
-
privileged: requestArgs.privileged,
|
|
3369
|
-
protocolID: requestArgs.protocolID,
|
|
3370
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3371
|
-
reason: requestArgs.privilegedReason,
|
|
3372
|
-
usageType: 'signing'
|
|
3373
|
-
});
|
|
3374
|
-
return await this.underlying.createSignature(...args);
|
|
3375
|
-
}
|
|
3376
|
-
async verifySignature(...args) {
|
|
3377
|
-
const [requestArgs, originator] = args;
|
|
3378
|
-
// Delegate to permission module if needed
|
|
3379
|
-
const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'verifySignature', requestArgs, originator, async (transformedArgs) => {
|
|
3380
|
-
return await this.underlying.verifySignature(transformedArgs, originator);
|
|
3381
|
-
});
|
|
3382
|
-
if (pModuleResult !== null) {
|
|
3383
|
-
return pModuleResult;
|
|
3384
|
-
}
|
|
3385
|
-
await this.ensureProtocolPermission({
|
|
3386
|
-
originator: originator,
|
|
3387
|
-
privileged: requestArgs.privileged,
|
|
3388
|
-
protocolID: requestArgs.protocolID,
|
|
3389
|
-
counterparty: requestArgs.counterparty || 'self',
|
|
3390
|
-
reason: requestArgs.privilegedReason,
|
|
3391
|
-
usageType: 'signing'
|
|
3392
|
-
});
|
|
3393
|
-
return await this.underlying.verifySignature(...args);
|
|
3394
|
-
}
|
|
3395
|
-
async acquireCertificate(...args) {
|
|
3396
|
-
const [requestArgs, originator] = args;
|
|
3397
|
-
if (this.config.seekCertificateAcquisitionPermissions) {
|
|
3398
|
-
await this.ensureProtocolPermission({
|
|
3399
|
-
originator: originator,
|
|
3400
|
-
privileged: requestArgs.privileged,
|
|
3401
|
-
protocolID: [1, `certificate acquisition ${requestArgs.type}`],
|
|
3402
|
-
counterparty: 'self',
|
|
3403
|
-
reason: requestArgs.privilegedReason,
|
|
3404
|
-
usageType: 'generic'
|
|
3405
|
-
});
|
|
3406
|
-
}
|
|
3407
|
-
return await this.underlying.acquireCertificate(...args);
|
|
3408
|
-
}
|
|
3409
|
-
async listCertificates(...args) {
|
|
3410
|
-
const [requestArgs, originator] = args;
|
|
3411
|
-
if (this.config.seekCertificateListingPermissions) {
|
|
3412
|
-
await this.ensureProtocolPermission({
|
|
3413
|
-
originator: originator,
|
|
3414
|
-
privileged: requestArgs.privileged,
|
|
3415
|
-
protocolID: [1, 'certificate list'],
|
|
3416
|
-
counterparty: 'self',
|
|
3417
|
-
reason: requestArgs.privilegedReason,
|
|
3418
|
-
usageType: 'generic'
|
|
3419
|
-
});
|
|
3420
|
-
}
|
|
3421
|
-
return await this.underlying.listCertificates(...args);
|
|
3422
|
-
}
|
|
3423
|
-
async proveCertificate(...args) {
|
|
3424
|
-
const [requestArgs, originator] = args;
|
|
3425
|
-
await this.ensureCertificateAccess({
|
|
3426
|
-
originator: originator,
|
|
3427
|
-
privileged: requestArgs.privileged,
|
|
3428
|
-
verifier: requestArgs.verifier,
|
|
3429
|
-
certType: requestArgs.certificate.type,
|
|
3430
|
-
fields: requestArgs.fieldsToReveal,
|
|
3431
|
-
reason: 'proveCertificate',
|
|
3432
|
-
usageType: 'disclosure'
|
|
3433
|
-
});
|
|
3434
|
-
return await this.underlying.proveCertificate(...args);
|
|
3435
|
-
}
|
|
3436
|
-
async relinquishCertificate(...args) {
|
|
3437
|
-
const [requestArgs, originator] = args;
|
|
3438
|
-
if (this.config.seekCertificateRelinquishmentPermissions) {
|
|
3439
|
-
await this.ensureProtocolPermission({
|
|
3440
|
-
originator: originator,
|
|
3441
|
-
privileged: !!requestArgs.privileged,
|
|
3442
|
-
protocolID: [1, `certificate relinquishment ${requestArgs.type}`],
|
|
3443
|
-
counterparty: 'self',
|
|
3444
|
-
reason: requestArgs.privilegedReason || 'relinquishCertificate',
|
|
3445
|
-
usageType: 'generic'
|
|
3446
|
-
});
|
|
3447
|
-
}
|
|
3448
|
-
return await this.underlying.relinquishCertificate(...args);
|
|
3449
|
-
}
|
|
3450
|
-
async discoverByIdentityKey(...args) {
|
|
3451
|
-
const [_, originator] = args;
|
|
3452
|
-
if (this.config.seekPermissionsForIdentityResolution) {
|
|
3453
|
-
await this.ensureProtocolPermission({
|
|
3454
|
-
originator: originator,
|
|
3455
|
-
privileged: false,
|
|
3456
|
-
protocolID: [1, 'identity resolution'],
|
|
3457
|
-
counterparty: 'self',
|
|
3458
|
-
reason: 'discoverByIdentityKey',
|
|
3459
|
-
usageType: 'generic'
|
|
3460
|
-
});
|
|
3461
|
-
}
|
|
3462
|
-
return await this.underlying.discoverByIdentityKey(...args);
|
|
3463
|
-
}
|
|
3464
|
-
async discoverByAttributes(...args) {
|
|
3465
|
-
const [_, originator] = args;
|
|
3466
|
-
if (this.config.seekPermissionsForIdentityResolution) {
|
|
3467
|
-
await this.ensureProtocolPermission({
|
|
3468
|
-
originator: originator,
|
|
3469
|
-
privileged: false,
|
|
3470
|
-
protocolID: [1, 'identity resolution'],
|
|
3471
|
-
counterparty: 'self',
|
|
3472
|
-
reason: 'discoverByAttributes',
|
|
3473
|
-
usageType: 'generic'
|
|
3474
|
-
});
|
|
3475
|
-
}
|
|
3476
|
-
return await this.underlying.discoverByAttributes(...args);
|
|
3477
|
-
}
|
|
3478
|
-
async isAuthenticated(...args) {
|
|
3479
|
-
return await this.underlying.isAuthenticated(...args);
|
|
3480
|
-
}
|
|
3481
|
-
async waitForAuthentication(...args) {
|
|
3482
|
-
const [_, originator] = args;
|
|
3483
|
-
if (this.config.seekGroupedPermission && originator) {
|
|
3484
|
-
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
3485
|
-
const normalized = normalizedOriginator;
|
|
3486
|
-
await this.withGroupedPermissionFlowLock(normalized, async () => {
|
|
3487
|
-
// 1. Fetch manifest.json from the originator
|
|
3488
|
-
const groupPermissions = await this.fetchManifestGroupPermissions(normalized);
|
|
3489
|
-
if (groupPermissions != null) {
|
|
3490
|
-
// 2. Filter out already-granted permissions
|
|
3491
|
-
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(normalized, groupPermissions);
|
|
3492
|
-
// 3. If any permissions are left to request, start the flow
|
|
3493
|
-
if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
3494
|
-
const key = `group:${normalized}`;
|
|
3495
|
-
if (this.activeRequests.has(key)) {
|
|
3496
|
-
// Another call is already waiting, piggyback on it
|
|
3497
|
-
await new Promise((resolve, reject) => {
|
|
3498
|
-
this.activeRequests.get(key).pending.push({ resolve, reject });
|
|
3499
|
-
});
|
|
3500
|
-
}
|
|
3501
|
-
else {
|
|
3502
|
-
// This is the first call, create a new request
|
|
3503
|
-
const promise = new Promise((resolve, reject) => {
|
|
3504
|
-
this.activeRequests.set(key, {
|
|
3505
|
-
request: { originator: normalized, permissions: permissionsToRequest },
|
|
3506
|
-
pending: [{ resolve, reject }]
|
|
3507
|
-
});
|
|
3508
|
-
});
|
|
3509
|
-
// Fire the event outside the executor to avoid async executor (S6544)
|
|
3510
|
-
await this.callEvent('onGroupedPermissionRequested', {
|
|
3511
|
-
requestID: key,
|
|
3512
|
-
originator: normalized,
|
|
3513
|
-
permissions: permissionsToRequest
|
|
3514
|
-
});
|
|
3515
|
-
await promise;
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
});
|
|
3520
|
-
}
|
|
3521
|
-
// Finally, after handling grouped permissions, call the underlying method.
|
|
3522
|
-
return await this.underlying.waitForAuthentication(...args);
|
|
3523
|
-
}
|
|
3524
|
-
async getHeight(...args) {
|
|
3525
|
-
return await this.underlying.getHeight(...args);
|
|
3526
|
-
}
|
|
3527
|
-
async getHeaderForHeight(...args) {
|
|
3528
|
-
return await this.underlying.getHeaderForHeight(...args);
|
|
3529
|
-
}
|
|
3530
|
-
async getNetwork(...args) {
|
|
3531
|
-
return await this.underlying.getNetwork(...args);
|
|
3532
|
-
}
|
|
3533
|
-
async getVersion(...args) {
|
|
3534
|
-
return await this.underlying.getVersion(...args);
|
|
3535
|
-
}
|
|
3536
|
-
/* ---------------------------------------------------------------------
|
|
3537
|
-
* 8) INTERNAL HELPER UTILITIES
|
|
3538
|
-
* --------------------------------------------------------------------- */
|
|
3539
|
-
/** Returns true if the specified origin is the admin originator. */
|
|
3540
|
-
isAdminOriginator(originator) {
|
|
3541
|
-
return this.normalizeOriginator(originator) === this.adminOriginator;
|
|
3542
|
-
}
|
|
3543
|
-
/**
|
|
3544
|
-
* Checks if the given protocol is admin-reserved per BRC-100 rules:
|
|
3545
|
-
*
|
|
3546
|
-
* - Must not start with `admin` (admin-reserved)
|
|
3547
|
-
* - Must not start with `p ` (allows for future specially permissioned protocols)
|
|
3548
|
-
*
|
|
3549
|
-
* If it violates these rules and the caller is not admin, we consider it "admin-only."
|
|
3550
|
-
*/
|
|
3551
|
-
isAdminProtocol(proto) {
|
|
3552
|
-
const protocolName = proto[1];
|
|
3553
|
-
if (protocolName.startsWith('admin')) {
|
|
3554
|
-
return true;
|
|
3555
|
-
}
|
|
3556
|
-
return false;
|
|
3557
|
-
}
|
|
3558
|
-
/**
|
|
3559
|
-
* Checks if the given label is admin-reserved per BRC-100 rules:
|
|
3560
|
-
*
|
|
3561
|
-
* - Must not start with `admin` (admin-reserved)
|
|
3562
|
-
* - Must not start with `p ` (permissioned labels requiring a permission module)
|
|
3563
|
-
*
|
|
3564
|
-
* If it violates these rules and the caller is not admin, we consider it "admin-only."
|
|
3565
|
-
*/
|
|
3566
|
-
isAdminLabel(label) {
|
|
3567
|
-
if (label.startsWith('admin')) {
|
|
3568
|
-
return true;
|
|
3569
|
-
}
|
|
3570
|
-
if (label.startsWith('p ')) {
|
|
3571
|
-
return true;
|
|
3572
|
-
}
|
|
3573
|
-
return false;
|
|
3574
|
-
}
|
|
3575
|
-
/**
|
|
3576
|
-
* Checks if the given basket is admin-reserved per BRC-100 rules:
|
|
3577
|
-
*
|
|
3578
|
-
* - Must not start with `admin`
|
|
3579
|
-
* - Must not be `default` (some wallets use this for internal operations)
|
|
3580
|
-
* - Must not start with `p ` (future specially permissioned baskets)
|
|
3581
|
-
*/
|
|
3582
|
-
isAdminBasket(basket) {
|
|
3583
|
-
if (basket === 'default')
|
|
3584
|
-
return true;
|
|
3585
|
-
if (basket.startsWith('admin'))
|
|
3586
|
-
return true;
|
|
3587
|
-
return false;
|
|
3588
|
-
}
|
|
3589
|
-
/**
|
|
3590
|
-
* Whether the permission is satisfied without consulting on-chain tokens:
|
|
3591
|
-
* cached, recently granted, or — when the user just granted it and its
|
|
3592
|
-
* token is still minting — after the in-flight mint settles. Spares the
|
|
3593
|
-
* user a duplicate prompt for a grant they already made moments ago.
|
|
3594
|
-
*/
|
|
3595
|
-
async hasRecentOrPendingGrant(cacheKey) {
|
|
3596
|
-
if (this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey))
|
|
3597
|
-
return true;
|
|
3598
|
-
const inFlight = this.mintsInFlight.get(cacheKey);
|
|
3599
|
-
if (inFlight == null)
|
|
3600
|
-
return false;
|
|
3601
|
-
await inFlight;
|
|
3602
|
-
return this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey);
|
|
3603
|
-
}
|
|
3604
|
-
/**
|
|
3605
|
-
* Returns true if we have a cached record that the permission identified by
|
|
3606
|
-
* `key` is valid and unexpired.
|
|
3607
|
-
*/
|
|
3608
|
-
isPermissionCached(key) {
|
|
3609
|
-
const entry = this.permissionCache.get(key);
|
|
3610
|
-
if (entry == null)
|
|
3611
|
-
return false;
|
|
3612
|
-
if (Date.now() - entry.cachedAt > WalletPermissionsManager.CACHE_TTL_MS) {
|
|
3613
|
-
this.permissionCache.delete(key);
|
|
3614
|
-
return false;
|
|
3615
|
-
}
|
|
3616
|
-
if (this.isTokenExpired(entry.expiry)) {
|
|
3617
|
-
this.permissionCache.delete(key);
|
|
3618
|
-
return false;
|
|
3619
|
-
}
|
|
3620
|
-
return true;
|
|
3621
|
-
}
|
|
3622
|
-
/** Caches the fact that the permission for `key` is valid until `expiry`. */
|
|
3623
|
-
cachePermission(key, expiry) {
|
|
3624
|
-
this.permissionCache.set(key, { expiry, cachedAt: Date.now() });
|
|
3625
|
-
}
|
|
3626
|
-
/** Records that a non-spending permission was just granted so we can skip re-prompting briefly. */
|
|
3627
|
-
markRecentGrant(request) {
|
|
3628
|
-
if (request.type === 'spending')
|
|
3629
|
-
return;
|
|
3630
|
-
const key = this.buildRequestKey(request);
|
|
3631
|
-
if (!key)
|
|
3632
|
-
return;
|
|
3633
|
-
this.recentGrants.set(key, Date.now() + WalletPermissionsManager.RECENT_GRANT_COVER_MS);
|
|
3634
|
-
}
|
|
3635
|
-
/** Returns true if we are inside the short "cover window" immediately after granting permission. */
|
|
3636
|
-
isRecentlyGranted(key) {
|
|
3637
|
-
const expiry = this.recentGrants.get(key);
|
|
3638
|
-
if (!expiry)
|
|
3639
|
-
return false;
|
|
3640
|
-
if (Date.now() > expiry) {
|
|
3641
|
-
this.recentGrants.delete(key);
|
|
3642
|
-
return false;
|
|
3643
|
-
}
|
|
3644
|
-
return true;
|
|
3645
|
-
}
|
|
3646
|
-
/** Normalizes and canonicalizes originator domains (e.g., lowercase + drop default ports). */
|
|
3647
|
-
normalizeOriginator(originator) {
|
|
3648
|
-
if (!originator)
|
|
3649
|
-
return '';
|
|
3650
|
-
const trimmed = originator.trim();
|
|
3651
|
-
if (!trimmed) {
|
|
3652
|
-
return '';
|
|
3653
|
-
}
|
|
3654
|
-
try {
|
|
3655
|
-
const hasScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed);
|
|
3656
|
-
const candidate = hasScheme ? trimmed : `https://${trimmed}`;
|
|
3657
|
-
const url = new URL(candidate);
|
|
3658
|
-
if (!url.hostname) {
|
|
3659
|
-
return trimmed.toLowerCase();
|
|
3660
|
-
}
|
|
3661
|
-
const hostname = url.hostname.toLowerCase();
|
|
3662
|
-
const needsBrackets = hostname.includes(':');
|
|
3663
|
-
const baseHost = needsBrackets ? `[${hostname}]` : hostname;
|
|
3664
|
-
const port = url.port;
|
|
3665
|
-
const defaultPort = WalletPermissionsManager.DEFAULT_PORTS[url.protocol];
|
|
3666
|
-
if (port && defaultPort && port === defaultPort) {
|
|
3667
|
-
return baseHost;
|
|
3668
|
-
}
|
|
3669
|
-
return port ? `${baseHost}:${port}` : baseHost;
|
|
3670
|
-
}
|
|
3671
|
-
catch {
|
|
3672
|
-
// Fall back to a conservative lowercase trim if URL parsing fails.
|
|
3673
|
-
return trimmed.toLowerCase();
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3676
|
-
isWhitelistedCounterpartyProtocol(counterparty, protocolID) {
|
|
3677
|
-
const whitelist = this.config.whitelistedCounterparties;
|
|
3678
|
-
if (whitelist == null)
|
|
3679
|
-
return false;
|
|
3680
|
-
if (!counterparty || counterparty === 'self' || counterparty === 'anyone')
|
|
3681
|
-
return false;
|
|
3682
|
-
const protocols = whitelist[counterparty] || whitelist[counterparty.toLowerCase()] || whitelist[counterparty.toUpperCase()];
|
|
3683
|
-
if (!protocols || protocols.length === 0)
|
|
3684
|
-
return false;
|
|
3685
|
-
const protoName = (protocolID?.[1] ?? '').toString().toLowerCase();
|
|
3686
|
-
if (!protoName)
|
|
3687
|
-
return false;
|
|
3688
|
-
return protocols.some(p => (p ?? '').toString().toLowerCase() === protoName);
|
|
3689
|
-
}
|
|
3690
|
-
/**
|
|
3691
|
-
* Produces a normalized originator value along with the set of legacy
|
|
3692
|
-
* representations that should be considered when searching for existing
|
|
3693
|
-
* permission tokens (for backwards compatibility).
|
|
3694
|
-
*/
|
|
3695
|
-
prepareOriginator(originator) {
|
|
3696
|
-
const trimmed = originator?.trim();
|
|
3697
|
-
if (!trimmed) {
|
|
3698
|
-
throw new Error('Originator is required for permission checks.');
|
|
3699
|
-
}
|
|
3700
|
-
const normalized = this.normalizeOriginator(trimmed) || trimmed.toLowerCase();
|
|
3701
|
-
const lookupValues = Array.from(new Set([trimmed, normalized])).filter(Boolean);
|
|
3702
|
-
return { normalized, lookupValues };
|
|
3703
|
-
}
|
|
3704
|
-
/**
|
|
3705
|
-
* Builds a unique list of originator variants that should be searched when
|
|
3706
|
-
* looking up on-chain tokens (e.g., legacy raw + normalized forms).
|
|
3707
|
-
*/
|
|
3708
|
-
buildOriginatorLookupValues(...origins) {
|
|
3709
|
-
const variants = new Set();
|
|
3710
|
-
for (const origin of origins) {
|
|
3711
|
-
const trimmed = origin?.trim();
|
|
3712
|
-
if (trimmed) {
|
|
3713
|
-
variants.add(trimmed);
|
|
3714
|
-
}
|
|
3715
|
-
}
|
|
3716
|
-
return Array.from(variants);
|
|
3717
|
-
}
|
|
3718
|
-
/**
|
|
3719
|
-
* Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
|
|
3720
|
-
* do not produce multiple user prompts.
|
|
3721
|
-
*/
|
|
3722
|
-
buildRequestKey(r) {
|
|
3723
|
-
const normalizedOriginator = this.normalizeOriginator(r.originator);
|
|
3724
|
-
switch (r.type) {
|
|
3725
|
-
case 'protocol':
|
|
3726
|
-
return `proto:${normalizedOriginator}:${!!r.privileged}:${r.protocolID?.join(',')}:${r.counterparty}`;
|
|
3727
|
-
case 'basket':
|
|
3728
|
-
return `basket:${normalizedOriginator}:${r.basket}`;
|
|
3729
|
-
case 'certificate':
|
|
3730
|
-
return `cert:${normalizedOriginator}:${!!r.privileged}:${r.certificate?.verifier}:${r.certificate?.certType}:${r.certificate?.fields.join('|')}`;
|
|
3731
|
-
case 'spending':
|
|
3732
|
-
return `spend:${normalizedOriginator}:${r.spending?.satoshis}`;
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
buildActiveRequestKey(r) {
|
|
3736
|
-
const base = this.buildRequestKey(r);
|
|
3737
|
-
if (r.type === 'protocol' || r.type === 'basket' || r.type === 'certificate') {
|
|
3738
|
-
return `${base}:${r.usageType ?? ''}`;
|
|
3739
|
-
}
|
|
3740
|
-
return base;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
exports.WalletPermissionsManager = WalletPermissionsManager;
|
|
3744
|
-
//# sourceMappingURL=WalletPermissionsManager.js.map
|