@bopen-io/wallet-toolbox 1.7.18
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/.claude/settings.local.json +10 -0
- package/.env.template +22 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/discussion.md +24 -0
- package/.github/pull_request_template.md +22 -0
- package/.github/workflows/push.yaml +145 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +280 -0
- package/CONTRIBUTING.md +89 -0
- package/README.md +43 -0
- package/docs/README.md +85 -0
- package/docs/client.md +19627 -0
- package/docs/monitor.md +953 -0
- package/docs/open-rpc/index.html +46 -0
- package/docs/services.md +6377 -0
- package/docs/setup.md +1268 -0
- package/docs/storage.md +5367 -0
- package/docs/wallet.md +19626 -0
- package/jest.config.ts +25 -0
- package/license.md +28 -0
- package/out/tsconfig.all.tsbuildinfo +1 -0
- package/package.json +63 -0
- package/src/CWIStyleWalletManager.ts +1999 -0
- package/src/Setup.ts +579 -0
- package/src/SetupClient.ts +322 -0
- package/src/SetupWallet.ts +108 -0
- package/src/SimpleWalletManager.ts +526 -0
- package/src/Wallet.ts +1169 -0
- package/src/WalletAuthenticationManager.ts +153 -0
- package/src/WalletLogger.ts +213 -0
- package/src/WalletPermissionsManager.ts +3660 -0
- package/src/WalletSettingsManager.ts +114 -0
- package/src/__tests/CWIStyleWalletManager.test.d.ts.map +1 -0
- package/src/__tests/CWIStyleWalletManager.test.js.map +1 -0
- package/src/__tests/CWIStyleWalletManager.test.ts +675 -0
- package/src/__tests/WalletPermissionsManager.callbacks.test.ts +323 -0
- package/src/__tests/WalletPermissionsManager.checks.test.ts +844 -0
- package/src/__tests/WalletPermissionsManager.encryption.test.ts +412 -0
- package/src/__tests/WalletPermissionsManager.fixtures.ts +307 -0
- package/src/__tests/WalletPermissionsManager.flows.test.ts +462 -0
- package/src/__tests/WalletPermissionsManager.initialization.test.ts +300 -0
- package/src/__tests/WalletPermissionsManager.pmodules.test.ts +798 -0
- package/src/__tests/WalletPermissionsManager.proxying.test.ts +724 -0
- package/src/__tests/WalletPermissionsManager.tokens.test.ts +503 -0
- package/src/index.all.ts +27 -0
- package/src/index.client.ts +25 -0
- package/src/index.mobile.ts +21 -0
- package/src/index.ts +1 -0
- package/src/monitor/Monitor.ts +412 -0
- package/src/monitor/MonitorDaemon.ts +188 -0
- package/src/monitor/README.md +3 -0
- package/src/monitor/__test/MonitorDaemon.man.test.ts +45 -0
- package/src/monitor/tasks/TaskCheckForProofs.ts +243 -0
- package/src/monitor/tasks/TaskCheckNoSends.ts +73 -0
- package/src/monitor/tasks/TaskClock.ts +33 -0
- package/src/monitor/tasks/TaskFailAbandoned.ts +54 -0
- package/src/monitor/tasks/TaskMonitorCallHistory.ts +26 -0
- package/src/monitor/tasks/TaskNewHeader.ts +93 -0
- package/src/monitor/tasks/TaskPurge.ts +68 -0
- package/src/monitor/tasks/TaskReorg.ts +89 -0
- package/src/monitor/tasks/TaskReviewStatus.ts +48 -0
- package/src/monitor/tasks/TaskSendWaiting.ts +122 -0
- package/src/monitor/tasks/TaskSyncWhenIdle.ts +26 -0
- package/src/monitor/tasks/TaskUnFail.ts +151 -0
- package/src/monitor/tasks/WalletMonitorTask.ts +47 -0
- package/src/sdk/CertOpsWallet.ts +18 -0
- package/src/sdk/PrivilegedKeyManager.ts +372 -0
- package/src/sdk/README.md +13 -0
- package/src/sdk/WERR_errors.ts +234 -0
- package/src/sdk/WalletError.ts +170 -0
- package/src/sdk/WalletErrorFromJson.ts +80 -0
- package/src/sdk/WalletServices.interfaces.ts +700 -0
- package/src/sdk/WalletSigner.interfaces.ts +11 -0
- package/src/sdk/WalletStorage.interfaces.ts +606 -0
- package/src/sdk/__test/CertificateLifeCycle.test.ts +131 -0
- package/src/sdk/__test/PrivilegedKeyManager.test.ts +738 -0
- package/src/sdk/__test/WalletError.test.ts +318 -0
- package/src/sdk/__test/validationHelpers.test.ts +21 -0
- package/src/sdk/index.ts +10 -0
- package/src/sdk/types.ts +226 -0
- package/src/services/README.md +11 -0
- package/src/services/ServiceCollection.ts +248 -0
- package/src/services/Services.ts +603 -0
- package/src/services/__tests/ARC.man.test.ts +123 -0
- package/src/services/__tests/ARC.timeout.man.test.ts +79 -0
- package/src/services/__tests/ArcGorillaPool.man.test.ts +108 -0
- package/src/services/__tests/arcServices.test.ts +8 -0
- package/src/services/__tests/bitrails.test.ts +56 -0
- package/src/services/__tests/getMerklePath.test.ts +15 -0
- package/src/services/__tests/getRawTx.test.ts +13 -0
- package/src/services/__tests/postBeef.test.ts +104 -0
- package/src/services/__tests/verifyBeef.test.ts +50 -0
- package/src/services/chaintracker/BHServiceClient.ts +212 -0
- package/src/services/chaintracker/ChaintracksChainTracker.ts +71 -0
- package/src/services/chaintracker/__tests/ChaintracksChainTracker.test.ts +33 -0
- package/src/services/chaintracker/__tests/ChaintracksServiceClient.test.ts +29 -0
- package/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.ts +72 -0
- package/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.ts +83 -0
- package/src/services/chaintracker/chaintracks/Api/BulkStorageApi.ts +92 -0
- package/src/services/chaintracker/chaintracks/Api/ChaintracksApi.ts +64 -0
- package/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.ts +189 -0
- package/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.ts +18 -0
- package/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.ts +58 -0
- package/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.ts +386 -0
- package/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.ts +25 -0
- package/src/services/chaintracker/chaintracks/Chaintracks.ts +609 -0
- package/src/services/chaintracker/chaintracks/ChaintracksService.ts +199 -0
- package/src/services/chaintracker/chaintracks/ChaintracksServiceClient.ts +154 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.ts +176 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.ts +174 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.ts +18 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.ts +113 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainWs.ts +81 -0
- package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.ts +86 -0
- package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorTeranodeP2P.ts +59 -0
- package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.ts +104 -0
- package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainWs.ts +66 -0
- package/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.ts +566 -0
- package/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.ts +219 -0
- package/src/services/chaintracker/chaintracks/Ingest/__tests/BulkIngestorCDNBabbage.test.ts +54 -0
- package/src/services/chaintracker/chaintracks/Ingest/__tests/LiveIngestorWhatsOnChainPoll.test.ts +33 -0
- package/src/services/chaintracker/chaintracks/Ingest/__tests/WhatsOnChainServices.test.ts +124 -0
- package/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.ts +92 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksKnexMigrations.ts +104 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.ts +382 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.ts +574 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.ts +438 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageMemory.ts +29 -0
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.ts +304 -0
- package/src/services/chaintracker/chaintracks/Storage/__tests/ChaintracksStorageIdb.test.ts +102 -0
- package/src/services/chaintracker/chaintracks/Storage/__tests/ChaintracksStorageKnex.test.ts +45 -0
- package/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.ts +77 -0
- package/src/services/chaintracker/chaintracks/__tests/ChaintracksClientApi.test.ts +192 -0
- package/src/services/chaintracker/chaintracks/__tests/LocalCdnServer.ts +75 -0
- package/src/services/chaintracker/chaintracks/__tests/createIdbChaintracks.test.ts +62 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNetBlockHeaders.json +1 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_0.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_1.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_2.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_3.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNetBlockHeaders.json +1 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_0.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_1.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_2.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_3.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNetBlockHeaders.json +1 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_0.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_1.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_2.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_3.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNetBlockHeaders.json +1 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_0.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_1.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_2.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_3.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_4.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNetBlockHeaders.json +1 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_0.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_1.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_2.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_3.headers +0 -0
- package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_4.headers +0 -0
- package/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.ts +92 -0
- package/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.ts +111 -0
- package/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.ts +91 -0
- package/src/services/chaintracker/chaintracks/createIdbChaintracks.ts +60 -0
- package/src/services/chaintracker/chaintracks/createKnexChaintracks.ts +65 -0
- package/src/services/chaintracker/chaintracks/createNoDbChaintracks.ts +60 -0
- package/src/services/chaintracker/chaintracks/index.all.ts +12 -0
- package/src/services/chaintracker/chaintracks/index.client.ts +4 -0
- package/src/services/chaintracker/chaintracks/index.mobile.ts +37 -0
- package/src/services/chaintracker/chaintracks/util/BulkFileDataManager.ts +975 -0
- package/src/services/chaintracker/chaintracks/util/BulkFileDataReader.ts +60 -0
- package/src/services/chaintracker/chaintracks/util/BulkFilesReader.ts +336 -0
- package/src/services/chaintracker/chaintracks/util/BulkHeaderFile.ts +247 -0
- package/src/services/chaintracker/chaintracks/util/ChaintracksFetch.ts +69 -0
- package/src/services/chaintracker/chaintracks/util/ChaintracksFs.ts +141 -0
- package/src/services/chaintracker/chaintracks/util/HeightRange.ts +153 -0
- package/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.ts +76 -0
- package/src/services/chaintracker/chaintracks/util/__tests/BulkFileDataManager.test.ts +304 -0
- package/src/services/chaintracker/chaintracks/util/__tests/ChaintracksFetch.test.ts +60 -0
- package/src/services/chaintracker/chaintracks/util/__tests/HeightRange.test.ts +67 -0
- package/src/services/chaintracker/chaintracks/util/__tests/SingleWriterMultiReaderLock.test.ts +49 -0
- package/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.ts +573 -0
- package/src/services/chaintracker/chaintracks/util/dirtyHashes.ts +29 -0
- package/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.ts +432 -0
- package/src/services/chaintracker/index.all.ts +4 -0
- package/src/services/chaintracker/index.client.ts +4 -0
- package/src/services/chaintracker/index.mobile.ts +4 -0
- package/src/services/createDefaultWalletServicesOptions.ts +77 -0
- package/src/services/index.ts +1 -0
- package/src/services/processingErrors/arcSuccessError.json +76 -0
- package/src/services/providers/ARC.ts +350 -0
- package/src/services/providers/Bitails.ts +256 -0
- package/src/services/providers/SdkWhatsOnChain.ts +83 -0
- package/src/services/providers/WhatsOnChain.ts +883 -0
- package/src/services/providers/__tests/WhatsOnChain.test.ts +242 -0
- package/src/services/providers/__tests/exchangeRates.test.ts +18 -0
- package/src/services/providers/exchangeRates.ts +265 -0
- package/src/services/providers/getBeefForTxid.ts +369 -0
- package/src/signer/README.md +5 -0
- package/src/signer/WalletSigner.ts +17 -0
- package/src/signer/methods/acquireDirectCertificate.ts +52 -0
- package/src/signer/methods/buildSignableTransaction.ts +183 -0
- package/src/signer/methods/completeSignedTransaction.ts +117 -0
- package/src/signer/methods/createAction.ts +172 -0
- package/src/signer/methods/internalizeAction.ts +106 -0
- package/src/signer/methods/proveCertificate.ts +43 -0
- package/src/signer/methods/signAction.ts +54 -0
- package/src/storage/README.md +14 -0
- package/src/storage/StorageIdb.ts +2304 -0
- package/src/storage/StorageKnex.ts +1425 -0
- package/src/storage/StorageProvider.ts +810 -0
- package/src/storage/StorageReader.ts +194 -0
- package/src/storage/StorageReaderWriter.ts +432 -0
- package/src/storage/StorageSyncReader.ts +34 -0
- package/src/storage/WalletStorageManager.ts +943 -0
- package/src/storage/__test/StorageIdb.test.ts +43 -0
- package/src/storage/__test/WalletStorageManager.test.ts +275 -0
- package/src/storage/__test/adminStats.man.test.ts +89 -0
- package/src/storage/__test/getBeefForTransaction.test.ts +385 -0
- package/src/storage/index.all.ts +11 -0
- package/src/storage/index.client.ts +7 -0
- package/src/storage/index.mobile.ts +6 -0
- package/src/storage/methods/ListActionsSpecOp.ts +70 -0
- package/src/storage/methods/ListOutputsSpecOp.ts +129 -0
- package/src/storage/methods/__test/GenerateChange/generateChangeSdk.test.ts +1057 -0
- package/src/storage/methods/__test/GenerateChange/randomValsUsed1.ts +20 -0
- package/src/storage/methods/__test/offsetKey.test.ts +274 -0
- package/src/storage/methods/attemptToPostReqsToNetwork.ts +389 -0
- package/src/storage/methods/createAction.ts +947 -0
- package/src/storage/methods/generateChange.ts +556 -0
- package/src/storage/methods/getBeefForTransaction.ts +139 -0
- package/src/storage/methods/getSyncChunk.ts +293 -0
- package/src/storage/methods/internalizeAction.ts +562 -0
- package/src/storage/methods/listActionsIdb.ts +183 -0
- package/src/storage/methods/listActionsKnex.ts +226 -0
- package/src/storage/methods/listCertificates.ts +73 -0
- package/src/storage/methods/listOutputsIdb.ts +203 -0
- package/src/storage/methods/listOutputsKnex.ts +263 -0
- package/src/storage/methods/offsetKey.ts +89 -0
- package/src/storage/methods/processAction.ts +420 -0
- package/src/storage/methods/purgeData.ts +251 -0
- package/src/storage/methods/purgeDataIdb.ts +10 -0
- package/src/storage/methods/reviewStatus.ts +101 -0
- package/src/storage/methods/reviewStatusIdb.ts +43 -0
- package/src/storage/methods/utils.Buffer.ts +33 -0
- package/src/storage/methods/utils.ts +56 -0
- package/src/storage/remoting/StorageClient.ts +567 -0
- package/src/storage/remoting/StorageMobile.ts +544 -0
- package/src/storage/remoting/StorageServer.ts +291 -0
- package/src/storage/remoting/__test/StorageClient.test.ts +113 -0
- package/src/storage/schema/KnexMigrations.ts +489 -0
- package/src/storage/schema/StorageIdbSchema.ts +150 -0
- package/src/storage/schema/entities/EntityBase.ts +210 -0
- package/src/storage/schema/entities/EntityCertificate.ts +188 -0
- package/src/storage/schema/entities/EntityCertificateField.ts +136 -0
- package/src/storage/schema/entities/EntityCommission.ts +148 -0
- package/src/storage/schema/entities/EntityOutput.ts +290 -0
- package/src/storage/schema/entities/EntityOutputBasket.ts +153 -0
- package/src/storage/schema/entities/EntityOutputTag.ts +121 -0
- package/src/storage/schema/entities/EntityOutputTagMap.ts +123 -0
- package/src/storage/schema/entities/EntityProvenTx.ts +319 -0
- package/src/storage/schema/entities/EntityProvenTxReq.ts +580 -0
- package/src/storage/schema/entities/EntitySyncState.ts +389 -0
- package/src/storage/schema/entities/EntityTransaction.ts +306 -0
- package/src/storage/schema/entities/EntityTxLabel.ts +121 -0
- package/src/storage/schema/entities/EntityTxLabelMap.ts +123 -0
- package/src/storage/schema/entities/EntityUser.ts +112 -0
- package/src/storage/schema/entities/MergeEntity.ts +73 -0
- package/src/storage/schema/entities/__tests/CertificateFieldTests.test.ts +353 -0
- package/src/storage/schema/entities/__tests/CertificateTests.test.ts +354 -0
- package/src/storage/schema/entities/__tests/CommissionTests.test.ts +371 -0
- package/src/storage/schema/entities/__tests/OutputBasketTests.test.ts +278 -0
- package/src/storage/schema/entities/__tests/OutputTagMapTests.test.ts +242 -0
- package/src/storage/schema/entities/__tests/OutputTagTests.test.ts +288 -0
- package/src/storage/schema/entities/__tests/OutputTests.test.ts +464 -0
- package/src/storage/schema/entities/__tests/ProvenTxReqTests.test.ts +340 -0
- package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +504 -0
- package/src/storage/schema/entities/__tests/SyncStateTests.test.ts +288 -0
- package/src/storage/schema/entities/__tests/TransactionTests.test.ts +604 -0
- package/src/storage/schema/entities/__tests/TxLabelMapTests.test.ts +361 -0
- package/src/storage/schema/entities/__tests/TxLabelTests.test.ts +198 -0
- package/src/storage/schema/entities/__tests/stampLogTests.test.ts +90 -0
- package/src/storage/schema/entities/__tests/usersTests.test.ts +340 -0
- package/src/storage/schema/entities/index.ts +16 -0
- package/src/storage/schema/tables/TableCertificate.ts +21 -0
- package/src/storage/schema/tables/TableCertificateField.ts +12 -0
- package/src/storage/schema/tables/TableCommission.ts +13 -0
- package/src/storage/schema/tables/TableMonitorEvent.ts +9 -0
- package/src/storage/schema/tables/TableOutput.ts +64 -0
- package/src/storage/schema/tables/TableOutputBasket.ts +12 -0
- package/src/storage/schema/tables/TableOutputTag.ts +10 -0
- package/src/storage/schema/tables/TableOutputTagMap.ts +9 -0
- package/src/storage/schema/tables/TableProvenTx.ts +14 -0
- package/src/storage/schema/tables/TableProvenTxReq.ts +65 -0
- package/src/storage/schema/tables/TableSettings.ts +17 -0
- package/src/storage/schema/tables/TableSyncState.ts +18 -0
- package/src/storage/schema/tables/TableTransaction.ts +54 -0
- package/src/storage/schema/tables/TableTxLabel.ts +10 -0
- package/src/storage/schema/tables/TableTxLabelMap.ts +9 -0
- package/src/storage/schema/tables/TableUser.ts +16 -0
- package/src/storage/schema/tables/index.ts +16 -0
- package/src/storage/sync/StorageMySQLDojoReader.ts +696 -0
- package/src/storage/sync/index.ts +1 -0
- package/src/utility/Format.ts +133 -0
- package/src/utility/README.md +3 -0
- package/src/utility/ReaderUint8Array.ts +187 -0
- package/src/utility/ScriptTemplateBRC29.ts +73 -0
- package/src/utility/__tests/utilityHelpers.noBuffer.test.ts +109 -0
- package/src/utility/aggregateResults.ts +68 -0
- package/src/utility/identityUtils.ts +159 -0
- package/src/utility/index.all.ts +7 -0
- package/src/utility/index.client.ts +7 -0
- package/src/utility/parseTxScriptOffsets.ts +29 -0
- package/src/utility/stampLog.ts +69 -0
- package/src/utility/tscProofToMerklePath.ts +48 -0
- package/src/utility/utilityHelpers.buffer.ts +34 -0
- package/src/utility/utilityHelpers.noBuffer.ts +60 -0
- package/src/utility/utilityHelpers.ts +275 -0
- package/src/wab-client/WABClient.ts +94 -0
- package/src/wab-client/__tests/WABClient.man.test.ts +59 -0
- package/src/wab-client/auth-method-interactors/AuthMethodInteractor.ts +47 -0
- package/src/wab-client/auth-method-interactors/DevConsoleInteractor.ts +73 -0
- package/src/wab-client/auth-method-interactors/PersonaIDInteractor.ts +35 -0
- package/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.ts +72 -0
- package/syncVersions.js +71 -0
- package/test/Wallet/StorageClient/storageClient.man.test.ts +75 -0
- package/test/Wallet/action/abortAction.test.ts +47 -0
- package/test/Wallet/action/createAction.test.ts +299 -0
- package/test/Wallet/action/createAction2.test.ts +1273 -0
- package/test/Wallet/action/createActionToGenerateBeefs.man.test.ts +293 -0
- package/test/Wallet/action/internalizeAction.a.test.ts +286 -0
- package/test/Wallet/action/internalizeAction.test.ts +682 -0
- package/test/Wallet/action/relinquishOutput.test.ts +37 -0
- package/test/Wallet/certificate/acquireCertificate.test.ts +298 -0
- package/test/Wallet/certificate/listCertificates.test.ts +346 -0
- package/test/Wallet/construct/Wallet.constructor.test.ts +57 -0
- package/test/Wallet/get/getHeaderForHeight.test.ts +82 -0
- package/test/Wallet/get/getHeight.test.ts +52 -0
- package/test/Wallet/get/getKnownTxids.test.ts +86 -0
- package/test/Wallet/get/getNetwork.test.ts +27 -0
- package/test/Wallet/get/getVersion.test.ts +27 -0
- package/test/Wallet/list/listActions.test.ts +279 -0
- package/test/Wallet/list/listActions2.test.ts +1381 -0
- package/test/Wallet/list/listCertificates.test.ts +118 -0
- package/test/Wallet/list/listOutputs.test.ts +447 -0
- package/test/Wallet/live/walletLive.man.test.ts +521 -0
- package/test/Wallet/local/localWallet.man.test.ts +93 -0
- package/test/Wallet/local/localWallet2.man.test.ts +277 -0
- package/test/Wallet/signAction/mountaintop.man.test.ts +130 -0
- package/test/Wallet/specOps/specOps.man.test.ts +220 -0
- package/test/Wallet/support/janitor.man.test.ts +40 -0
- package/test/Wallet/support/operations.man.test.ts +407 -0
- package/test/Wallet/support/reqErrorReview.2025.05.06.man.test.ts +347 -0
- package/test/Wallet/sync/Wallet.sync.test.ts +215 -0
- package/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.ts +203 -0
- package/test/Wallet/sync/setActive.test.ts +170 -0
- package/test/WalletClient/LocalKVStore.man.test.ts +114 -0
- package/test/WalletClient/WERR.man.test.ts +35 -0
- package/test/bsv-ts-sdk/LocalKVStore.test.ts +102 -0
- package/test/checkDB.ts +57 -0
- package/test/checkdb +0 -0
- package/test/examples/backup.man.test.ts +59 -0
- package/test/examples/pushdrop.test.ts +282 -0
- package/test/monitor/Monitor.test.ts +620 -0
- package/test/services/Services.test.ts +263 -0
- package/test/storage/KnexMigrations.test.ts +86 -0
- package/test/storage/StorageMySQLDojoReader.man.test.ts +60 -0
- package/test/storage/count.test.ts +177 -0
- package/test/storage/find.test.ts +195 -0
- package/test/storage/findLegacy.test.ts +67 -0
- package/test/storage/idb/allocateChange.test.ts +251 -0
- package/test/storage/idb/count.test.ts +158 -0
- package/test/storage/idb/find.test.ts +177 -0
- package/test/storage/idb/idbSpeed.test.ts +36 -0
- package/test/storage/idb/insert.test.ts +268 -0
- package/test/storage/idb/transactionAbort.test.ts +108 -0
- package/test/storage/idb/update.test.ts +999 -0
- package/test/storage/insert.test.ts +278 -0
- package/test/storage/update.test.ts +1021 -0
- package/test/storage/update2.test.ts +897 -0
- package/test/utils/TestUtilsWalletStorage.ts +2526 -0
- package/test/utils/localWalletMethods.ts +363 -0
- package/test/utils/removeFailedFromDatabase.sql +17 -0
- package/ts2md.json +44 -0
- package/tsconfig.all.json +31 -0
- package/tsconfig.client.json +29 -0
- package/tsconfig.json +17 -0
- package/tsconfig.mobile.json +28 -0
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
import { mockUnderlyingWallet, MockedBSV_SDK } from './WalletPermissionsManager.fixtures'
|
|
2
|
+
import { WalletPermissionsManager, PermissionToken } from '../WalletPermissionsManager'
|
|
3
|
+
|
|
4
|
+
jest.mock('@bsv/sdk', () => MockedBSV_SDK)
|
|
5
|
+
|
|
6
|
+
describe('WalletPermissionsManager - Permission Checks', () => {
|
|
7
|
+
let underlying: jest.Mocked<any>
|
|
8
|
+
let manager: WalletPermissionsManager
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Fresh mock wallet before each test
|
|
12
|
+
underlying = mockUnderlyingWallet() as jest.Mocked<any>
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.clearAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/* ------------------------------------------------------
|
|
20
|
+
* 5) PROTOCOL USAGE (DPACP) TESTS
|
|
21
|
+
* ------------------------------------------------------ */
|
|
22
|
+
describe('Protocol Usage (DPACP)', () => {
|
|
23
|
+
it('should skip permission prompt if secLevel=0 (open usage)', async () => {
|
|
24
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
25
|
+
seekProtocolPermissionsForSigning: true // Typically enforced
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Attempt createSignature with protocolID=[0, "someProtocol"]
|
|
29
|
+
// Because securityLevel=0, the manager should skip checks
|
|
30
|
+
await expect(
|
|
31
|
+
manager.createSignature(
|
|
32
|
+
{
|
|
33
|
+
protocolID: [0, 'open-protocol'],
|
|
34
|
+
data: [0x01, 0x02],
|
|
35
|
+
keyID: '1'
|
|
36
|
+
},
|
|
37
|
+
'some-user.com'
|
|
38
|
+
)
|
|
39
|
+
).resolves.not.toThrow()
|
|
40
|
+
|
|
41
|
+
// No permission request
|
|
42
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
43
|
+
expect(activeRequests.size).toBe(0)
|
|
44
|
+
|
|
45
|
+
// Underlying createSignature called once
|
|
46
|
+
expect(underlying.createSignature).toHaveBeenCalledTimes(1)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should prompt for protocol usage if securityLevel=1 and no existing token', async () => {
|
|
50
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
51
|
+
seekProtocolPermissionsForSigning: true
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// We'll bind a callback that grants ephemeral permission automatically
|
|
55
|
+
manager.bindCallback('onProtocolPermissionRequested', async request => {
|
|
56
|
+
// For tests, automatically grant ephemeral permission
|
|
57
|
+
await manager.grantPermission({
|
|
58
|
+
requestID: request.requestID,
|
|
59
|
+
ephemeral: true
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Because secLevel=1, we need a valid DPACP token
|
|
64
|
+
// We have no token => manager triggers a request => callback grants ephemeral => passes
|
|
65
|
+
await expect(
|
|
66
|
+
manager.createSignature(
|
|
67
|
+
{
|
|
68
|
+
protocolID: [1, 'test-protocol'],
|
|
69
|
+
data: [0x99, 0xaa],
|
|
70
|
+
keyID: '1'
|
|
71
|
+
},
|
|
72
|
+
'some-nonadmin.com'
|
|
73
|
+
)
|
|
74
|
+
).resolves.not.toThrow()
|
|
75
|
+
|
|
76
|
+
// The underlying signature should succeed
|
|
77
|
+
expect(underlying.createSignature).toHaveBeenCalledTimes(1)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should deny protocol usage if user denies permission', async () => {
|
|
81
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {})
|
|
82
|
+
|
|
83
|
+
// The callback denies the request
|
|
84
|
+
manager.bindCallback('onProtocolPermissionRequested', request => {
|
|
85
|
+
manager.denyPermission(request.requestID)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Attempt an operation that requires protocol permission
|
|
89
|
+
await expect(
|
|
90
|
+
manager.encrypt(
|
|
91
|
+
{
|
|
92
|
+
protocolID: [1, 'needs-perm'],
|
|
93
|
+
plaintext: [1, 2, 3],
|
|
94
|
+
keyID: 'xyz'
|
|
95
|
+
},
|
|
96
|
+
'external-app.com'
|
|
97
|
+
)
|
|
98
|
+
).rejects.toThrow(/Permission denied/)
|
|
99
|
+
|
|
100
|
+
// Underlying encrypt was never called
|
|
101
|
+
expect(underlying.encrypt).toHaveBeenCalledTimes(0)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should enforce privileged token if differentiatePrivilegedOperations=true', async () => {
|
|
105
|
+
// By default, differentiatePrivilegedOperations is true.
|
|
106
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
107
|
+
seekProtocolPermissionsForSigning: true
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
manager.bindCallback('onProtocolPermissionRequested', async req => {
|
|
111
|
+
// The request has `privileged=true`, so the resulting token must also be privileged.
|
|
112
|
+
// We'll grant ephemeral to simulate success quickly.
|
|
113
|
+
await manager.grantPermission({
|
|
114
|
+
requestID: req.requestID,
|
|
115
|
+
ephemeral: true
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Attempt a privileged signature
|
|
120
|
+
await expect(
|
|
121
|
+
manager.createSignature(
|
|
122
|
+
{
|
|
123
|
+
protocolID: [1, 'high-level-crypto'],
|
|
124
|
+
privileged: true,
|
|
125
|
+
data: [0xc0, 0xff, 0xee],
|
|
126
|
+
keyID: '1'
|
|
127
|
+
},
|
|
128
|
+
'nonadmin.app'
|
|
129
|
+
)
|
|
130
|
+
).resolves.not.toThrow()
|
|
131
|
+
|
|
132
|
+
// Confirm underlying was ultimately called
|
|
133
|
+
expect(underlying.createSignature).toHaveBeenCalledTimes(1)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should ignore `privileged=true` if differentiatePrivilegedOperations=false', async () => {
|
|
137
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
138
|
+
differentiatePrivilegedOperations: false, // Forces privileged usage to be treated as non-privileged
|
|
139
|
+
seekProtocolPermissionsForSigning: true
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Because we treat privileged as false, the permission request does not need privileged credentials.
|
|
143
|
+
manager.bindCallback('onProtocolPermissionRequested', async req => {
|
|
144
|
+
await manager.grantPermission({
|
|
145
|
+
requestID: req.requestID,
|
|
146
|
+
ephemeral: true
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await expect(
|
|
151
|
+
manager.createSignature(
|
|
152
|
+
{
|
|
153
|
+
protocolID: [1, 'some-protocol'],
|
|
154
|
+
privileged: true, // This flag will be ignored
|
|
155
|
+
data: [0x99],
|
|
156
|
+
keyID: 'keyXYZ'
|
|
157
|
+
},
|
|
158
|
+
'nonadmin.com'
|
|
159
|
+
)
|
|
160
|
+
).resolves.not.toThrow()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should fail if protocol name is admin-reserved and caller is not admin', async () => {
|
|
164
|
+
// admin-reserved means protocol name starts with "admin" or "p ".
|
|
165
|
+
manager = new WalletPermissionsManager(underlying, 'secure.admin.com')
|
|
166
|
+
|
|
167
|
+
// Non-admin tries to do e.g. `createHmac` with protocol name "admin super-secret"
|
|
168
|
+
await expect(
|
|
169
|
+
manager.createHmac(
|
|
170
|
+
{
|
|
171
|
+
protocolID: [1, 'admin super-secret'],
|
|
172
|
+
data: [0x01, 0x02],
|
|
173
|
+
keyID: '1'
|
|
174
|
+
},
|
|
175
|
+
'not-an-admin.com'
|
|
176
|
+
)
|
|
177
|
+
).rejects.toThrow(/admin-only/i)
|
|
178
|
+
|
|
179
|
+
// Underlying call never invoked
|
|
180
|
+
expect(underlying.createHmac).toHaveBeenCalledTimes(0)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should prompt for renewal if token is found but expired', async () => {
|
|
184
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {})
|
|
185
|
+
|
|
186
|
+
// Suppose the user already had a token but it’s expired. We mock `findProtocolToken` so that
|
|
187
|
+
// it returns an expired token, forcing a renewal request.
|
|
188
|
+
const expiredToken: PermissionToken = {
|
|
189
|
+
tx: [],
|
|
190
|
+
txid: 'oldtxid123',
|
|
191
|
+
outputIndex: 0,
|
|
192
|
+
outputScript: 'deadbeef',
|
|
193
|
+
satoshis: 1,
|
|
194
|
+
originator: 'some-nonadmin.com',
|
|
195
|
+
expiry: 1, // definitely in the past
|
|
196
|
+
privileged: false,
|
|
197
|
+
securityLevel: 1,
|
|
198
|
+
protocol: 'test-protocol',
|
|
199
|
+
counterparty: 'self'
|
|
200
|
+
}
|
|
201
|
+
jest.spyOn(manager as any, 'findProtocolToken').mockResolvedValue(expiredToken)
|
|
202
|
+
|
|
203
|
+
// We'll bind a callback that grants a renewal ephemeral
|
|
204
|
+
manager.bindCallback('onProtocolPermissionRequested', async req => {
|
|
205
|
+
expect(req.renewal).toBe(true)
|
|
206
|
+
expect(req.previousToken).toEqual(expiredToken)
|
|
207
|
+
await manager.grantPermission({
|
|
208
|
+
requestID: req.requestID,
|
|
209
|
+
ephemeral: true
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Now call an operation that requires protocol usage
|
|
214
|
+
await manager.createSignature(
|
|
215
|
+
{
|
|
216
|
+
protocolID: [1, 'test-protocol'],
|
|
217
|
+
data: [0xfe],
|
|
218
|
+
keyID: '1'
|
|
219
|
+
},
|
|
220
|
+
'some-nonadmin.com'
|
|
221
|
+
)
|
|
222
|
+
// Should succeed after renewal
|
|
223
|
+
expect(underlying.createSignature).toHaveBeenCalledTimes(1)
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
/* ------------------------------------------------------
|
|
228
|
+
* 6) BASKET USAGE (DBAP) TESTS
|
|
229
|
+
* ------------------------------------------------------ */
|
|
230
|
+
describe('Basket Usage (DBAP)', () => {
|
|
231
|
+
it('should fail immediately if using an admin-only basket as non-admin', async () => {
|
|
232
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com')
|
|
233
|
+
// Attempt to createAction to insert into "admin secret-basket" from a non-admin origin
|
|
234
|
+
await expect(
|
|
235
|
+
manager.createAction(
|
|
236
|
+
{
|
|
237
|
+
description: 'Insert into admin basket',
|
|
238
|
+
outputs: [
|
|
239
|
+
{
|
|
240
|
+
lockingScript: 'abcd',
|
|
241
|
+
satoshis: 100,
|
|
242
|
+
basket: 'admin secret-basket',
|
|
243
|
+
outputDescription: 'Nothing to see here'
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
'non-admin.com'
|
|
248
|
+
)
|
|
249
|
+
).rejects.toThrow(/admin-only/i)
|
|
250
|
+
|
|
251
|
+
// Underlying createAction never called
|
|
252
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(0)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should fail immediately if using the reserved basket "default" as non-admin', async () => {
|
|
256
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com')
|
|
257
|
+
await expect(
|
|
258
|
+
manager.createAction(
|
|
259
|
+
{
|
|
260
|
+
description: 'Insert to default basket',
|
|
261
|
+
outputs: [
|
|
262
|
+
{
|
|
263
|
+
lockingScript: '0x1234',
|
|
264
|
+
satoshis: 1,
|
|
265
|
+
basket: 'default',
|
|
266
|
+
outputDescription: 'Nothing to see here'
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
'some-nonadmin.com'
|
|
271
|
+
)
|
|
272
|
+
).rejects.toThrow(/admin-only/i)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('should prompt for insertion permission if seekBasketInsertionPermissions=true', async () => {
|
|
276
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
277
|
+
seekBasketInsertionPermissions: true
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// auto-grant ephemeral
|
|
281
|
+
manager.bindCallback('onBasketAccessRequested', async req => {
|
|
282
|
+
await manager.grantPermission({
|
|
283
|
+
requestID: req.requestID,
|
|
284
|
+
ephemeral: true
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// Also auto-grant unrelated spending authorization (since this is createAction)
|
|
289
|
+
manager.bindCallback('onSpendingAuthorizationRequested', async req => {
|
|
290
|
+
await manager.grantPermission({
|
|
291
|
+
requestID: req.requestID,
|
|
292
|
+
ephemeral: true
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await expect(
|
|
297
|
+
manager.createAction(
|
|
298
|
+
{
|
|
299
|
+
description: 'Insert to user-basket',
|
|
300
|
+
outputs: [
|
|
301
|
+
{
|
|
302
|
+
lockingScript: '7812',
|
|
303
|
+
satoshis: 1,
|
|
304
|
+
basket: 'user-basket',
|
|
305
|
+
outputDescription: 'Nothing to see here'
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
'some-nonadmin.com'
|
|
310
|
+
)
|
|
311
|
+
).resolves.not.toThrow()
|
|
312
|
+
|
|
313
|
+
// Confirm underlying createAction was eventually invoked
|
|
314
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('should skip insertion permission if seekBasketInsertionPermissions=false', async () => {
|
|
318
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
319
|
+
seekBasketInsertionPermissions: false
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// Auto-grant unrelated spending authorization (since this is createAction)
|
|
323
|
+
manager.bindCallback('onSpendingAuthorizationRequested', async req => {
|
|
324
|
+
await manager.grantPermission({
|
|
325
|
+
requestID: req.requestID,
|
|
326
|
+
ephemeral: true
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
await manager.createAction(
|
|
331
|
+
{
|
|
332
|
+
description: 'Insert to user-basket',
|
|
333
|
+
outputs: [
|
|
334
|
+
{
|
|
335
|
+
lockingScript: '1234',
|
|
336
|
+
satoshis: 1,
|
|
337
|
+
basket: 'some-basket',
|
|
338
|
+
outputDescription: 'Nothing to see here'
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
'nonadmin.com'
|
|
343
|
+
)
|
|
344
|
+
// No requests queued, underlying is called
|
|
345
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
346
|
+
expect(activeRequests.size).toBe(0)
|
|
347
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should require listing permission if seekBasketListingPermissions=true and no token', async () => {
|
|
351
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
352
|
+
seekBasketListingPermissions: true
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
manager.bindCallback('onBasketAccessRequested', async req => {
|
|
356
|
+
// Deny for test
|
|
357
|
+
manager.denyPermission(req.requestID)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// Attempt to list a user basket
|
|
361
|
+
await expect(manager.listOutputs({ basket: 'user-basket' }, 'some-user.com')).rejects.toThrow(/Permission denied/)
|
|
362
|
+
|
|
363
|
+
// There is one underlying call: internally, we called listOutputs to check if we had permission
|
|
364
|
+
// (we did not, we sought it, and the user denied). So we see this call here, but we DO NOT see
|
|
365
|
+
// the actual proxied call (for listing outputs in user-basket), since it was denied.
|
|
366
|
+
expect(underlying.listOutputs).toHaveBeenCalledTimes(1)
|
|
367
|
+
expect(underlying.listOutputs).toHaveBeenLastCalledWith(
|
|
368
|
+
{
|
|
369
|
+
basket: 'admin basket-access',
|
|
370
|
+
include: 'entire transactions',
|
|
371
|
+
tagQueryMode: 'all',
|
|
372
|
+
tags: ['originator some-user.com', 'basket user-basket']
|
|
373
|
+
},
|
|
374
|
+
'admin.com'
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('should prompt for removal permission if seekBasketRemovalPermissions=true', async () => {
|
|
379
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
380
|
+
seekBasketRemovalPermissions: true
|
|
381
|
+
})
|
|
382
|
+
manager.bindCallback('onBasketAccessRequested', async req => {
|
|
383
|
+
// auto-grant ephemeral
|
|
384
|
+
await manager.grantPermission({
|
|
385
|
+
requestID: req.requestID,
|
|
386
|
+
ephemeral: true
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
await expect(
|
|
391
|
+
manager.relinquishOutput(
|
|
392
|
+
{
|
|
393
|
+
output: 'someTxid.1',
|
|
394
|
+
basket: 'user-basket'
|
|
395
|
+
},
|
|
396
|
+
'some-user.com'
|
|
397
|
+
)
|
|
398
|
+
).resolves.not.toThrow()
|
|
399
|
+
|
|
400
|
+
expect(underlying.relinquishOutput).toHaveBeenCalledTimes(1)
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
/* ------------------------------------------------------
|
|
405
|
+
* 7) CERTIFICATE USAGE (DCAP) TESTS
|
|
406
|
+
* ------------------------------------------------------ */
|
|
407
|
+
describe('Certificate Usage (DCAP)', () => {
|
|
408
|
+
it('should skip certificate disclosure permission if config.seekCertificateDisclosurePermissions=false', async () => {
|
|
409
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
410
|
+
seekCertificateDisclosurePermissions: false
|
|
411
|
+
})
|
|
412
|
+
// Directly call proveCertificate with no token => no prompt => immediate success
|
|
413
|
+
await expect(
|
|
414
|
+
manager.proveCertificate(
|
|
415
|
+
{
|
|
416
|
+
certificate: {
|
|
417
|
+
type: 'KYC',
|
|
418
|
+
subject: '02abcdef...',
|
|
419
|
+
serialNumber: '123',
|
|
420
|
+
certifier: '02ccc...',
|
|
421
|
+
fields: { name: 'Alice', dob: '2000-01-01' }
|
|
422
|
+
},
|
|
423
|
+
fieldsToReveal: ['name'],
|
|
424
|
+
verifier: '02xyz...',
|
|
425
|
+
privileged: false
|
|
426
|
+
},
|
|
427
|
+
'nonadmin.com'
|
|
428
|
+
)
|
|
429
|
+
).resolves.not.toThrow()
|
|
430
|
+
|
|
431
|
+
expect(underlying.proveCertificate).toHaveBeenCalledTimes(1)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('should require permission if seekCertificateDisclosurePermissions=true, no valid token', async () => {
|
|
435
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
436
|
+
seekCertificateDisclosurePermissions: true
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// Auto-grant ephemeral for test
|
|
440
|
+
manager.bindCallback('onCertificateAccessRequested', async req => {
|
|
441
|
+
await manager.grantPermission({
|
|
442
|
+
requestID: req.requestID,
|
|
443
|
+
ephemeral: true
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
// Because we don't have a stored token, it triggers request -> ephemeral granted -> success
|
|
448
|
+
await manager.proveCertificate(
|
|
449
|
+
{
|
|
450
|
+
certificate: {
|
|
451
|
+
type: 'KYC',
|
|
452
|
+
subject: '02abc..',
|
|
453
|
+
serialNumber: 'xyz',
|
|
454
|
+
certifier: '02dddd...',
|
|
455
|
+
fields: { name: 'Bob', nationality: 'Mars' }
|
|
456
|
+
},
|
|
457
|
+
fieldsToReveal: ['name'],
|
|
458
|
+
verifier: '02xxxx..',
|
|
459
|
+
privileged: false
|
|
460
|
+
},
|
|
461
|
+
'some-user.com'
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
expect(underlying.proveCertificate).toHaveBeenCalledTimes(1)
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('should check that requested fields are a subset of the token’s fields', async () => {
|
|
468
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
469
|
+
seekCertificateDisclosurePermissions: true
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// Suppose we find an existing token that covers fields: ['name', 'dob', 'nationality']
|
|
473
|
+
const existingToken: PermissionToken = {
|
|
474
|
+
tx: [],
|
|
475
|
+
txid: 'aabbcc',
|
|
476
|
+
outputIndex: 0,
|
|
477
|
+
outputScript: 'scriptHex',
|
|
478
|
+
satoshis: 1,
|
|
479
|
+
originator: 'some-user.com',
|
|
480
|
+
expiry: 9999999999, // not expired
|
|
481
|
+
privileged: false,
|
|
482
|
+
certType: 'KYC',
|
|
483
|
+
certFields: ['name', 'dob', 'nationality'],
|
|
484
|
+
verifier: '02eeee...'
|
|
485
|
+
}
|
|
486
|
+
jest
|
|
487
|
+
.spyOn(manager as any, 'findCertificateToken')
|
|
488
|
+
.mockImplementation(async (orig, priv, verif, ct, requestedFields) => {
|
|
489
|
+
// if requestedFields includes "someMissingField", return undefined
|
|
490
|
+
// else return the existingToken
|
|
491
|
+
if ((requestedFields as string[]).includes('someMissingField')) {
|
|
492
|
+
return undefined // forces a request
|
|
493
|
+
}
|
|
494
|
+
return existingToken // forces immediate success
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
// Attempt to prove certificate revealing only 'name' -> Should pass without prompt
|
|
498
|
+
await manager.proveCertificate(
|
|
499
|
+
{
|
|
500
|
+
certificate: {
|
|
501
|
+
type: 'KYC',
|
|
502
|
+
certifier: '02eeee...',
|
|
503
|
+
subject: '02some...',
|
|
504
|
+
serialNumber: '',
|
|
505
|
+
fields: { name: 'Charlie', dob: '1999-01-01', nationality: 'EU' }
|
|
506
|
+
},
|
|
507
|
+
fieldsToReveal: ['name'],
|
|
508
|
+
verifier: '02eeee...',
|
|
509
|
+
privileged: false
|
|
510
|
+
},
|
|
511
|
+
'some-user.com'
|
|
512
|
+
)
|
|
513
|
+
expect(underlying.proveCertificate).toHaveBeenCalledTimes(1)
|
|
514
|
+
|
|
515
|
+
// Attempt to reveal a field the token does NOT cover -> triggers request
|
|
516
|
+
// Since the existing token does not cover 'someMissingField', we expect a prompt. Let’s deny it:
|
|
517
|
+
manager.bindCallback('onCertificateAccessRequested', async req => {
|
|
518
|
+
manager.denyPermission(req.requestID)
|
|
519
|
+
})
|
|
520
|
+
const secondAttempt = manager.proveCertificate(
|
|
521
|
+
{
|
|
522
|
+
certificate: {
|
|
523
|
+
type: 'KYC',
|
|
524
|
+
certifier: '02eeee...',
|
|
525
|
+
fields: { name: 'Charlie', dob: '1999-01-01', nationality: 'EU' }
|
|
526
|
+
},
|
|
527
|
+
fieldsToReveal: ['dob', 'someMissingField'],
|
|
528
|
+
verifier: '02eeee...',
|
|
529
|
+
privileged: false
|
|
530
|
+
},
|
|
531
|
+
'some-user.com'
|
|
532
|
+
)
|
|
533
|
+
await expect(secondAttempt).rejects.toThrow(/Permission denied/)
|
|
534
|
+
|
|
535
|
+
// Underlying proveCertificate not called for second attempt
|
|
536
|
+
expect(underlying.proveCertificate).toHaveBeenCalledTimes(1)
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it('should prompt for renewal if token is expired', async () => {
|
|
540
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
541
|
+
seekCertificateDisclosurePermissions: true
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
// Mock an expired token
|
|
545
|
+
const expiredCertToken: PermissionToken = {
|
|
546
|
+
tx: [],
|
|
547
|
+
txid: 'old-expired',
|
|
548
|
+
outputIndex: 0,
|
|
549
|
+
outputScript: 'deadbeef',
|
|
550
|
+
satoshis: 1,
|
|
551
|
+
originator: 'app.com',
|
|
552
|
+
expiry: 1,
|
|
553
|
+
privileged: false,
|
|
554
|
+
certType: 'KYC',
|
|
555
|
+
certFields: ['name', 'dob'],
|
|
556
|
+
verifier: '02verifier'
|
|
557
|
+
}
|
|
558
|
+
jest.spyOn(manager as any, 'findCertificateToken').mockResolvedValue(expiredCertToken)
|
|
559
|
+
|
|
560
|
+
// Callback that grants renewal ephemeral
|
|
561
|
+
manager.bindCallback('onCertificateAccessRequested', async req => {
|
|
562
|
+
expect(req.renewal).toBe(true)
|
|
563
|
+
await manager.grantPermission({
|
|
564
|
+
requestID: req.requestID,
|
|
565
|
+
ephemeral: true
|
|
566
|
+
})
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
await manager.proveCertificate(
|
|
570
|
+
{
|
|
571
|
+
certificate: {
|
|
572
|
+
type: 'KYC',
|
|
573
|
+
fields: { name: 'Bob', dob: '1970' },
|
|
574
|
+
certifier: '02verifier'
|
|
575
|
+
},
|
|
576
|
+
fieldsToReveal: ['name'],
|
|
577
|
+
verifier: '02verifier',
|
|
578
|
+
privileged: false
|
|
579
|
+
},
|
|
580
|
+
'app.com'
|
|
581
|
+
)
|
|
582
|
+
// Succeeds after ephemeral renewal
|
|
583
|
+
expect(underlying.proveCertificate).toHaveBeenCalledTimes(1)
|
|
584
|
+
})
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
/* ------------------------------------------------------
|
|
588
|
+
* 8) SPENDING AUTHORIZATION (DSAP) TESTS
|
|
589
|
+
* ------------------------------------------------------ */
|
|
590
|
+
describe('Spending Authorization (DSAP)', () => {
|
|
591
|
+
it('should skip if seekSpendingPermissions=false', async () => {
|
|
592
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
593
|
+
seekSpendingPermissions: false
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
// createAction that tries to net spend 200 sats
|
|
597
|
+
const result = await manager.createAction(
|
|
598
|
+
{
|
|
599
|
+
description: 'Some spend transaction',
|
|
600
|
+
outputs: [
|
|
601
|
+
{
|
|
602
|
+
lockingScript: '1321',
|
|
603
|
+
satoshis: 200,
|
|
604
|
+
outputDescription: 'Nothing to see here'
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
},
|
|
608
|
+
'user.com'
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
// No prompt triggered
|
|
612
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
613
|
+
expect(activeRequests.size).toBe(0)
|
|
614
|
+
|
|
615
|
+
// Underlying createAction definitely called
|
|
616
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
617
|
+
// If seekSpendingPermissions=false, the result should NOT? contain the signableTransaction
|
|
618
|
+
expect(result.signableTransaction).not.toBeDefined()
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
it('should require spending token if netSpent > 0 and seekSpendingPermissions=true', async () => {
|
|
622
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
623
|
+
seekSpendingPermissions: true
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
// We’ll also mock the signableTransaction return to help manager compute netSpent
|
|
627
|
+
underlying.createAction.mockResolvedValueOnce({
|
|
628
|
+
signableTransaction: {
|
|
629
|
+
tx: [0x00], // minimal
|
|
630
|
+
reference: 'ref1'
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
// The manager tries to parse the transaction to find netSpent.
|
|
634
|
+
// By default, netSpent = totalOutput + fee - totalExplicitInputs
|
|
635
|
+
// We haven't provided any explicit inputs in the createAction call, so netSpent = 200 + fee
|
|
636
|
+
|
|
637
|
+
// Auto-grant ephemeral for test
|
|
638
|
+
manager.bindCallback('onSpendingAuthorizationRequested', async req => {
|
|
639
|
+
await manager.grantPermission({
|
|
640
|
+
requestID: req.requestID,
|
|
641
|
+
ephemeral: true,
|
|
642
|
+
amount: 1000
|
|
643
|
+
})
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
await expect(
|
|
647
|
+
manager.createAction(
|
|
648
|
+
{
|
|
649
|
+
description: 'Spend 200 sats with no input from user',
|
|
650
|
+
outputs: [
|
|
651
|
+
{
|
|
652
|
+
outputDescription: 'Nothing to see here',
|
|
653
|
+
lockingScript: '1abc',
|
|
654
|
+
satoshis: 200
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
},
|
|
658
|
+
'some-user.com'
|
|
659
|
+
)
|
|
660
|
+
).resolves.not.toThrow()
|
|
661
|
+
|
|
662
|
+
// underlying createAction called
|
|
663
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
it('should check monthly limit usage and prompt renewal if insufficient', async () => {
|
|
667
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com')
|
|
668
|
+
|
|
669
|
+
// Suppose we find an existing DSAP token with authorizedAmount=500
|
|
670
|
+
// manager.findSpendingToken() is used internally, so let's mock it
|
|
671
|
+
const existingSpendingToken: PermissionToken = {
|
|
672
|
+
tx: [],
|
|
673
|
+
txid: 'dsap-old',
|
|
674
|
+
outputIndex: 0,
|
|
675
|
+
outputScript: 'scriptHex',
|
|
676
|
+
satoshis: 1,
|
|
677
|
+
originator: 'shopper.com',
|
|
678
|
+
authorizedAmount: 500,
|
|
679
|
+
expiry: 0 // indefinite
|
|
680
|
+
}
|
|
681
|
+
jest.spyOn(manager as any, 'findSpendingToken').mockResolvedValue(existingSpendingToken)
|
|
682
|
+
|
|
683
|
+
// Next, manager.querySpentSince(token) sums the user’s monthly spending from labeled actions
|
|
684
|
+
// Let’s stub that to say they've already spent 400.
|
|
685
|
+
jest.spyOn(manager as any, 'querySpentSince').mockResolvedValue(400)
|
|
686
|
+
|
|
687
|
+
// Attempt spending 200 => total usage would be 600 which exceeds 500 => prompt renewal
|
|
688
|
+
// We'll auto-deny for test
|
|
689
|
+
manager.bindCallback('onSpendingAuthorizationRequested', req => {
|
|
690
|
+
manager.denyPermission(req.requestID)
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
await expect(
|
|
694
|
+
manager.createAction(
|
|
695
|
+
{
|
|
696
|
+
description: 'Buy something for 200 sats',
|
|
697
|
+
outputs: [
|
|
698
|
+
{
|
|
699
|
+
outputDescription: 'Nothing to see here',
|
|
700
|
+
lockingScript: 'op_return',
|
|
701
|
+
satoshis: 200
|
|
702
|
+
}
|
|
703
|
+
]
|
|
704
|
+
},
|
|
705
|
+
'shopper.com'
|
|
706
|
+
)
|
|
707
|
+
).rejects.toThrow(/Permission denied/)
|
|
708
|
+
|
|
709
|
+
// The underlying createAction call was started but the manager calls abortAction upon denial
|
|
710
|
+
expect(underlying.abortAction).toHaveBeenCalledTimes(1)
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
it('should pass if usage plus new spend is within the monthly limit', async () => {
|
|
714
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {})
|
|
715
|
+
|
|
716
|
+
// existing DSAP token with authorizedAmount=1000
|
|
717
|
+
const dsapToken: PermissionToken = {
|
|
718
|
+
tx: [],
|
|
719
|
+
txid: 'dsap123',
|
|
720
|
+
outputIndex: 0,
|
|
721
|
+
outputScript: '9218',
|
|
722
|
+
satoshis: 1,
|
|
723
|
+
originator: 'shopper.com',
|
|
724
|
+
authorizedAmount: 1000,
|
|
725
|
+
expiry: 0
|
|
726
|
+
}
|
|
727
|
+
jest.spyOn(manager as any, 'findSpendingToken').mockResolvedValue(dsapToken)
|
|
728
|
+
|
|
729
|
+
// Suppose they've spent 200 so far
|
|
730
|
+
jest.spyOn(manager as any, 'querySpentSince').mockResolvedValue(200)
|
|
731
|
+
|
|
732
|
+
// Attempt new spending of 500 => total=700 which is <= 1000 => no prompt
|
|
733
|
+
await manager.createAction(
|
|
734
|
+
{
|
|
735
|
+
description: 'Spend 500 sats',
|
|
736
|
+
outputs: [
|
|
737
|
+
{
|
|
738
|
+
outputDescription: 'Nothing to see here',
|
|
739
|
+
lockingScript: '0abc',
|
|
740
|
+
satoshis: 500
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
},
|
|
744
|
+
'shopper.com'
|
|
745
|
+
)
|
|
746
|
+
// Success, no new permission requested
|
|
747
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
748
|
+
expect(activeRequests.size).toBe(0)
|
|
749
|
+
|
|
750
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
751
|
+
})
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
/* ------------------------------------------------------
|
|
755
|
+
* 9) LABEL USAGE PERMISSION TESTS
|
|
756
|
+
* ------------------------------------------------------ */
|
|
757
|
+
describe('Label Usage Permission', () => {
|
|
758
|
+
it('should fail if label starts with "admin" and caller is not admin', async () => {
|
|
759
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com')
|
|
760
|
+
|
|
761
|
+
// Attempt to createAction with a label "admin secret-stuff"
|
|
762
|
+
await expect(
|
|
763
|
+
manager.createAction(
|
|
764
|
+
{
|
|
765
|
+
description: 'Applying admin label?',
|
|
766
|
+
labels: ['admin secret-stuff']
|
|
767
|
+
},
|
|
768
|
+
'nonadmin.com'
|
|
769
|
+
)
|
|
770
|
+
).rejects.toThrow(/admin-only/)
|
|
771
|
+
|
|
772
|
+
// Underlying createAction never called
|
|
773
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(0)
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
it('should skip label permission if seekPermissionWhenApplyingActionLabels=false', async () => {
|
|
777
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
778
|
+
seekPermissionWhenApplyingActionLabels: false
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
// Non-admin applies label "my-app-label"
|
|
782
|
+
await expect(
|
|
783
|
+
manager.createAction({ description: 'Add label', labels: ['my-app-label'] }, 'some-app.com')
|
|
784
|
+
).resolves.not.toThrow()
|
|
785
|
+
|
|
786
|
+
// No prompt
|
|
787
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
788
|
+
expect(activeRequests.size).toBe(0)
|
|
789
|
+
|
|
790
|
+
// Called underlying
|
|
791
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
it('should prompt for label usage if seekPermissionWhenApplyingActionLabels=true', async () => {
|
|
795
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
796
|
+
seekPermissionWhenApplyingActionLabels: true
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
manager.bindCallback('onProtocolPermissionRequested', async req => {
|
|
800
|
+
// This request will have protocolID=[1, "action label <label>"], etc.
|
|
801
|
+
await manager.grantPermission({
|
|
802
|
+
requestID: req.requestID,
|
|
803
|
+
ephemeral: true
|
|
804
|
+
})
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
await manager.createAction(
|
|
808
|
+
{
|
|
809
|
+
description: 'Add label "user-label-123"',
|
|
810
|
+
labels: ['user-label-123']
|
|
811
|
+
},
|
|
812
|
+
'nonadmin.com'
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
// Underlying is called
|
|
816
|
+
expect(underlying.createAction).toHaveBeenCalledTimes(1)
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
it('should also prompt for listing actions by label if seekPermissionWhenListingActionsByLabel=true', async () => {
|
|
820
|
+
manager = new WalletPermissionsManager(underlying, 'admin.com', {
|
|
821
|
+
seekPermissionWhenListingActionsByLabel: true
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
manager.bindCallback('onProtocolPermissionRequested', async req => {
|
|
825
|
+
// auto-grant ephemeral
|
|
826
|
+
await manager.grantPermission({
|
|
827
|
+
requestID: req.requestID,
|
|
828
|
+
ephemeral: true
|
|
829
|
+
})
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
await expect(
|
|
833
|
+
manager.listActions(
|
|
834
|
+
{
|
|
835
|
+
labels: ['search-this-label']
|
|
836
|
+
},
|
|
837
|
+
'external-app.com'
|
|
838
|
+
)
|
|
839
|
+
).resolves.not.toThrow()
|
|
840
|
+
|
|
841
|
+
expect(underlying.listActions).toHaveBeenCalledTimes(1)
|
|
842
|
+
})
|
|
843
|
+
})
|
|
844
|
+
})
|