Skip to content

Commit 9e91527

Browse files
Merge branch 'develop'
2 parents 74e4015 + 4ba4796 commit 9e91527

File tree

10 files changed

+151
-45
lines changed

10 files changed

+151
-45
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@smontero/smartvaults-js-client",
3-
"version": "0.0.62",
3+
"version": "0.0.63",
44
"description": "SmartVaults javascript client",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/SmartVaults.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Authenticator, DirectPrivateKeyAuthenticator } from '@smontero/nostr-ua
22
import { generatePrivateKey, Kind, Event, Filter, Sub } from 'nostr-tools'
33
import { SmartVaultsKind, TagType, ProposalType, ProposalStatus, ApprovalStatus, StoreKind, AuthenticatorType, NetworkType, FiatCurrency, Magic } from './enum'
44
import { NostrClient, PubPool, Store } from './service'
5-
import { buildEvent, filterBuilder, getTagValues, PaginationOpts, fromNostrDate, toPublished, nostrDate, isNip05Verified, type singleKindFilterParams, FilterBuilder, TimeUtil, CurrencyUtil, getTagValue, DoublyLinkedList } from './util'
5+
import { buildEvent, filterBuilder, getTagValues, PaginationOpts, fromNostrDate, nostrDate, isNip05Verified, type singleKindFilterParams, FilterBuilder, TimeUtil, CurrencyUtil, getTagValue, DoublyLinkedList } from './util'
66
import { BasicTrxDetails, BaseOwnedSigner, BaseSharedSigner, BitcoinUtil, Contact, Policy, PublishedPolicy, TrxDetails } from './models'
77
import * as SmartVaultsTypes from './types'
88
import { EventKindHandlerFactory } from './event-kind-handler'
@@ -520,7 +520,7 @@ export class SmartVaults {
520520
* const ids = ['id1', 'id2'];
521521
* const policiesById = await getPoliciesById(ids);
522522
*/
523-
async getPoliciesById(ids: string[]): Promise<Map<string, PublishedPolicy>> {
523+
getPoliciesById = async (ids: string[]): Promise<Map<string, PublishedPolicy>> => {
524524
const store = this.getStore(SmartVaultsKind.Policy)
525525
const missingIds = store.missing(ids)
526526
if (missingIds.length) {
@@ -697,6 +697,7 @@ export class SmartVaults {
697697
to_address,
698698
amount,
699699
psbt,
700+
policy_path: policyPath,
700701
...keyAgentPayment
701702
}
702703
} as SmartVaultsTypes.KeyAgentPaymentProposal;
@@ -707,7 +708,8 @@ export class SmartVaults {
707708
description,
708709
to_address,
709710
amount,
710-
psbt
711+
psbt,
712+
policy_path: policyPath,
711713
}
712714
} as SmartVaultsTypes.SpendingProposal;
713715
}
@@ -731,15 +733,13 @@ export class SmartVaults {
731733

732734
await this.getChat().sendMessage(msg, policy.id)
733735

734-
const signer = 'Unknown'
735736
const fee = Number(this.bitcoinUtil.getFee(psbt))
736737
const utxo = this.bitcoinUtil.getPsbtUtxos(psbt)
737738
const [amountFiat, feeFiat] = await this.bitcoinExchangeRate.convertToFiat([amount, fee])
738739
const bitcoinExchangeRate = await this.bitcoinExchangeRate.getExchangeRate()
739740
const activeFiatCurrency = this.bitcoinExchangeRate.getActiveFiatCurrency()
740741
const status = ProposalStatus.Unsigned
741742
const commonProps = {
742-
signer,
743743
amountFiat,
744744
fee,
745745
feeFiat,
@@ -752,17 +752,20 @@ export class SmartVaults {
752752
activeFiatCurrency,
753753
}
754754

755+
const ownedSigners = await this.getOwnedSigners()
755756
let publishedProposal: SmartVaultsTypes.PublishedSpendingProposal | SmartVaultsTypes.PublishedKeyAgentPaymentProposal
756757
if (keyAgentPayment) {
757758
publishedProposal = {
758759
...proposalContent[ProposalType.KeyAgentPayment],
759760
...commonProps,
761+
signers: await policy.getExpectedSigners(proposalContent[ProposalType.KeyAgentPayment], ownedSigners),
760762
type: ProposalType.KeyAgentPayment,
761763
} as SmartVaultsTypes.PublishedKeyAgentPaymentProposal
762764
} else {
763765
publishedProposal = {
764766
...proposalContent[ProposalType.Spending],
765767
...commonProps,
768+
signers: await policy.getExpectedSigners(proposalContent[ProposalType.Spending], ownedSigners),
766769
type: ProposalType.Spending,
767770
} as SmartVaultsTypes.PublishedSpendingProposal
768771
}
@@ -1964,7 +1967,7 @@ export class SmartVaults {
19641967
if (!sharedKeyAuthenticator) {
19651968
throw new Error(`Shared key for policy with id ${policy_id} not found`)
19661969
}
1967-
const policy = toPublished(await sharedKeyAuthenticator.decryptObj(policyEvent.content), policyEvent)
1970+
const policy = (await this.getPoliciesById([policy_id])).get(policy_id)!
19681971
const type = ProposalType.ProofOfReserve
19691972
//proposal = policy.proof_of_reserve(wallet,message)
19701973
const proposal: SmartVaultsTypes.ProofOfReserveProposal = {
@@ -1994,10 +1997,11 @@ export class SmartVaults {
19941997
await this.getChat().sendMessage(msg, policy.id)
19951998
const proposal_id = proposalEvent.id
19961999
const status = ProposalStatus.Unsigned
1997-
const signer = 'Unknown'
2000+
const ownedSigners = await this.getOwnedSigners()
2001+
const signers: string[] = await policy.getExpectedSigners(proposal[type], ownedSigners)
19982002
const fee = this.bitcoinUtil.getFee(psbt)
19992003
const utxos = this.bitcoinUtil.getPsbtUtxos(psbt)
2000-
return { ...proposal[type], proposal_id, type, status, signer, fee, utxos, policy_id, createdAt }
2004+
return { ...proposal[type], proposal_id, type, status, signers, fee, utxos, policy_id, createdAt }
20012005

20022006
}
20032007

src/event-kind-handler/EventKindHandlerFactory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class EventKindHandlerFactory {
5555
const deleteSignerOfferings = this.smartVaults.deleteSignerOfferings
5656
const getPolicyMembers = this.smartVaults.getPolicyMembers
5757
const saveTransactionMetadata = this.smartVaults.saveTransactionMetadata
58+
const getPoliciesById = this.smartVaults.getPoliciesById
5859
const eventsStore = stores.get(StoreKind.Events)!
5960
const completedProposalsStore = stores.get(SmartVaultsKind.CompletedProposal)!
6061
const proposalsStore = stores.get(SmartVaultsKind.Proposal)!
@@ -68,7 +69,7 @@ export class EventKindHandlerFactory {
6869
getSharedKeysById, getCompletedProposalsByPolicyId, getProposalsByPolicyId, getApprovalsByPolicyId, getSharedSigners, getOwnedSigners, getTransactionMetadataByPolicyId, saveTransactionMetadata))
6970
break
7071
case SmartVaultsKind.Proposal:
71-
this.handlers.set(eventKind, new ProposalHandler(stores.get(eventKind)!, eventsStore, approvalsStore, nostrClient, bitcoinUtil, authenticator, getSharedKeysById, checkPsbts, getOwnedSigners, getApprovalsByProposalId))
72+
this.handlers.set(eventKind, new ProposalHandler(stores.get(eventKind)!, eventsStore, approvalsStore, nostrClient, bitcoinUtil, authenticator, getSharedKeysById, checkPsbts, getOwnedSigners, getApprovalsByProposalId, getPoliciesById))
7273
break
7374
case SmartVaultsKind.ApprovedProposal:
7475
this.handlers.set(eventKind, new ApprovalsHandler(stores.get(eventKind)!, eventsStore, nostrClient, authenticator, getSharedKeysById))

src/event-kind-handler/ProposalHandler.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { type SpendingProposal, type PublishedSpendingProposal, type PublishedPr
44
import { type Store, type NostrClient } from '../service'
55
import { getTagValues, fromNostrDate, buildEvent, BitcoinExchangeRate } from '../util'
66
import { EventKindHandler } from './EventKindHandler'
7-
import { type BitcoinUtil } from '../models'
7+
import { type PublishedPolicy, type BitcoinUtil } from '../models'
88
import { type Authenticator } from '@smontero/nostr-ual'
9+
910
export class ProposalHandler extends EventKindHandler {
1011
private readonly store: Store
1112
private readonly eventsStore: Store
@@ -18,9 +19,18 @@ export class ProposalHandler extends EventKindHandler {
1819
private readonly getOwnedSigners: () => Promise<PublishedOwnedSigner[]>
1920
private readonly getApprovalsByProposalId: (proposal_ids?: string[] | string) => Promise<Map<string, Array<PublishedApprovedProposal>>>
2021
private readonly bitcoinExchangeRate: BitcoinExchangeRate = BitcoinExchangeRate.getInstance();
21-
constructor(store: Store, eventsStore: Store, approvalsStore: Store, nostrClient: NostrClient, bitcoinUtil: BitcoinUtil, authenticator: Authenticator, getSharedKeysById: (ids: string[]) => Promise<Map<string, SharedKeyAuthenticator>>, checkPsbts: (proposalId: string) => Promise<boolean>,
22+
private readonly getPoliciesById: (policy_ids: string[]) => Promise<Map<string, PublishedPolicy>>
23+
constructor(
24+
store: Store,
25+
eventsStore: Store,
26+
approvalsStore: Store,
27+
nostrClient: NostrClient,
28+
bitcoinUtil: BitcoinUtil,
29+
authenticator: Authenticator,
30+
getSharedKeysById: (ids: string[]) => Promise<Map<string, SharedKeyAuthenticator>>, checkPsbts: (proposalId: string) => Promise<boolean>,
2231
getOwnedSigners: () => Promise<Array<PublishedOwnedSigner>>,
23-
getApprovalsByProposalId: (proposal_ids?: string[] | string) => Promise<Map<string, Array<PublishedApprovedProposal>>>) {
32+
getApprovalsByProposalId: (proposal_ids?: string[] | string) => Promise<Map<string, Array<PublishedApprovedProposal>>>,
33+
getPoliciesById: (policy_ids: string[]) => Promise<Map<string, PublishedPolicy>>) {
2434
super()
2535
this.store = store
2636
this.eventsStore = eventsStore
@@ -32,15 +42,7 @@ export class ProposalHandler extends EventKindHandler {
3242
this.checkPsbts = checkPsbts
3343
this.getOwnedSigners = getOwnedSigners
3444
this.getApprovalsByProposalId = getApprovalsByProposalId
35-
}
36-
37-
private searchSignerInDescriptor(fingerprints: string[], descriptor: string): string | null {
38-
for (const fingerprint of fingerprints) {
39-
if (descriptor.includes(fingerprint)) {
40-
return fingerprint
41-
}
42-
}
43-
return null
45+
this.getPoliciesById = getPoliciesById
4446
}
4547

4648
protected async _handle<K extends number>(proposalEvents: Array<Event<K>>): Promise<Array<ActivePublishedProposal | PublishedKeyAgentPaymentProposal>> {
@@ -52,18 +54,14 @@ export class ProposalHandler extends EventKindHandler {
5254
this.checkPsbts(proposalId).then(status => ({ proposalId, status: status ? ProposalStatus.Signed : ProposalStatus.Unsigned }))
5355
);
5456

55-
const [statusResults, signers] = await Promise.all([
56-
Promise.all(statusPromises),
57-
this.getOwnedSigners()
58-
]);
57+
const [statusResults, ownedSigners] = await Promise.all([await Promise.all(statusPromises), this.getOwnedSigners()])
5958

6059
const bitcoinExchangeRate = await this.bitcoinExchangeRate.getExchangeRate();
6160
const activeFiatCurrency = this.bitcoinExchangeRate.getActiveFiatCurrency();
6261
const proposalsStatusMap = new Map(statusResults.map(res => [res.proposalId, res.status]));
6362

6463
const decryptedProposals: Array<ActivePublishedProposal | PublishedKeyAgentPaymentProposal> = []
6564
const rawEvents: Array<Event<K>> = []
66-
const fingerprints: string[] = signers.map(signer => signer.fingerprint)
6765

6866
const decryptPromises: Promise<any>[] = proposalEvents.map(async (proposalEvent) => {
6967
const storedProposal: PublishedSpendingProposal | PublishedKeyAgentPaymentProposal = this.store.get(proposalEvent.id, 'proposal_id')
@@ -112,8 +110,8 @@ export class ProposalHandler extends EventKindHandler {
112110
const type = Object.keys(decryptedProposalObj)[0] as ProposalType;
113111
const proposalContent = decryptedProposalObj[type]
114112
const createdAt = fromNostrDate(proposalEvent.created_at)
115-
const signerResult: string | null = this.searchSignerInDescriptor(fingerprints, proposalContent.descriptor)
116-
const signer = signerResult ?? 'Unknown'
113+
const policy = (await this.getPoliciesById([policyId])).get(policyId)
114+
const signers = await policy?.getExpectedSigners(proposalContent, ownedSigners) || []
117115
const psbt = proposalContent.psbt
118116
const utxos = this.bitcoinUtil.getPsbtUtxos(psbt)
119117
const fee = Number(this.bitcoinUtil.getFee(psbt))
@@ -122,7 +120,7 @@ export class ProposalHandler extends EventKindHandler {
122120
let publishedProposal: ActivePublishedProposal;
123121
const commonProps = {
124122
type,
125-
signer,
123+
signers,
126124
fee,
127125
utxos,
128126
createdAt,

src/models/PublishedPolicy.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { DirectPrivateKeyAuthenticator } from '@smontero/nostr-ual'
99
import { Keys, Store } from '../service'
1010
import { fromNostrDate } from '../util'
1111
import { SmartVaults } from '../SmartVaults'
12-
import { LabeledUtxo } from '../types'
12+
import { LabeledUtxo, PublishedOwnedSigner } from '../types'
1313

1414
describe('PublishedPolicy', () => {
1515
let policyContent: Policy
@@ -190,8 +190,7 @@ describe('PublishedPolicy', () => {
190190
wallet.sync.mockResolvedValue()
191191
const expected = { amount: 3000, psbt: "psbt2" }
192192
wallet.build_trx.mockResolvedValue(expected)
193-
let policyPath = new Map<string, Array<number>>()
194-
policyPath.set("83aswe", [1])
193+
let policyPath = { "83aswe": [1] }
195194
let utxos = ["05dce7f5440ded30bd55359d9e4f65de34fefaaef5fb16ac4cfaf72375fd204d:1", "123ce7f5440ded30bd55359d9e4f65de34fefaaef5fb16ac4cfaf72375fd204d:2"]
196195
let frozenUtxos = ["15dce7f5440ded30bd55359d9e4f65de34fefaaef5fb16ac4cfaf72375fd204g:3"]
197196
let actual = await policy.buildTrx({
@@ -511,6 +510,36 @@ describe('PublishedPolicy', () => {
511510
})
512511
})
513512

513+
describe('getExpectedSigners', () => {
514+
515+
it('should return the expected signers when theres no policy path', async () => {
516+
const signer1 = { name: 'SmartVaults', description: undefined, fingerprint: 'f57a6b99', descriptor: "tr([f57a6b99/86'/1'/784923']tpubDC45v32EZGP2U4qVTK…tbexZUMtY4ubZGS74kQftEGibUxUpybMan7/0/*)#jakwhh0u", t: 'Seed' } as PublishedOwnedSigner
517+
const signer2 = { name: 'SmartVaults', description: undefined, fingerprint: 'f3ab64d8', descriptor: "tr([f3ab64d8/86'/1'/784923']tpubDCh4uyVDVretfgTNka…91gN5LYtuSCbr1Vo6mzQmD49sF2vGpReZp2/0/*)#yavh9uq5", t: 'Seed' } as PublishedOwnedSigner
518+
smartVaults.getOwnedSigners.mockResolvedValue([signer1, signer2])
519+
jest.spyOn(policy, 'getPolicy').mockReturnValue(undefined!)
520+
const proposal = { descriptor: "descriptor" + signer1.fingerprint }
521+
const actual = await policy.getExpectedSigners(proposal, [signer1, signer2])
522+
expect([signer1.fingerprint]).toEqual(actual)
523+
})
524+
525+
it('should return the expected signers when theres policy path', async () => {
526+
const signer1 = { name: 'SmartVaults', description: undefined, fingerprint: 'f57a6b99', descriptor: "tr([f57a6b99/86'/1'/784923']tpubDC45v32EZGP2U4qVTK…tbexZUMtY4ubZGS74kQftEGibUxUpybMan7/0/*)#jakwhh0u", t: 'Seed' } as PublishedOwnedSigner
527+
const signer2 = { name: 'SmartVaults', description: undefined, fingerprint: 'f3ab64d8', descriptor: "tr([f3ab64d8/86'/1'/784923']tpubDCh4uyVDVretfgTNka…91gN5LYtuSCbr1Vo6mzQmD49sF2vGpReZp2/0/*)#yavh9uq5", t: 'Seed' } as PublishedOwnedSigner
528+
smartVaults.getOwnedSigners.mockResolvedValue([signer1, signer2])
529+
const policyPath = { "83aswe": [0], "a2A8ds": [2], "dsdnii": [0] }
530+
const proposal = { descriptor: "descriptor" + signer1.fingerprint, policy_path: policyPath }
531+
const policyTree = new Map<string, any>()
532+
policyTree.set("id", "83aswe")
533+
const lastItem = new Map<string, any>()
534+
lastItem.set("id", "any")
535+
lastItem.set("fingerprint", "f3ab64d8")
536+
policyTree.set("items", [new Map<string, any>([["id", "a2A8ds"], ["items", [new Map(), new Map(), lastItem]]]),])
537+
jest.spyOn(policy, 'getPolicy').mockReturnValue(policyTree)
538+
const actual = await policy.getExpectedSigners(proposal, [signer1, signer2])
539+
expect([signer2.fingerprint]).toEqual(actual)
540+
})
541+
})
542+
514543
describe('Augmented transaction details', () => {
515544

516545
let date1;

src/models/PublishedPolicy.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { BaseOwnedSigner, PolicyPathSelector, Trx, Policy, FinalizeTrxResponse,
55
import { BitcoinUtil, Wallet } from './interfaces'
66
import { CurrencyUtil, PaginationOpts, TimeUtil, fromNostrDate, toPublished } from '../util'
77
import { generateUiMetadata, UIMetadata, Key } from '../util/GenerateUiMetadata'
8-
import { LabeledUtxo, PublishedTransactionMetadata, PublishedOwnedSigner, PublishedSharedSigner, PublishedSpendingProposal, ActivePublishedProposal, TransactionMetadata } from '../types'
8+
import { LabeledUtxo, PublishedTransactionMetadata, PublishedOwnedSigner, PublishedSharedSigner, PublishedSpendingProposal, ActivePublishedProposal, TransactionMetadata, PolicyPath, Item } from '../types'
99
import { type Store } from '../service'
1010
import { StringUtil } from '../util'
1111
import { BitcoinExchangeRate } from '../util'
@@ -194,7 +194,7 @@ export class PublishedPolicy {
194194
address: string,
195195
amount: string,
196196
feeRate: string,
197-
policyPath?: Map<string, Array<number>>,
197+
policyPath?: PolicyPath,
198198
utxos?: Array<string>,
199199
frozenUtxos?: Array<string>
200200
}): Promise<Trx> {
@@ -777,4 +777,72 @@ export class PublishedPolicy {
777777
return labeledAddress
778778
}))
779779
}
780+
781+
private addFingerprints = (item: Item, policyPath: PolicyPath, fingerprints: Set<string>) => {
782+
if (item.has('fingerprint')) {
783+
fingerprints.add(item.get('fingerprint'))
784+
}
785+
let items: Item[] = [];
786+
const maybePolicyPathIndices = policyPath[item.get('id')]
787+
if (maybePolicyPathIndices) {
788+
for (const index of maybePolicyPathIndices) {
789+
items.push(item.get('items')[index])
790+
}
791+
} else {
792+
items = item.get('items')
793+
}
794+
if (!items?.length) return
795+
if (items.some(item => policyPath[item.get('id')])) return
796+
for (const item of items) {
797+
this.addFingerprints(item, policyPath, fingerprints)
798+
}
799+
}
800+
801+
private searchSignerInDescriptor(fingerprints: string[], descriptor: string): string[] {
802+
const result: string[] = []
803+
for (const fingerprint of fingerprints) {
804+
if (descriptor.includes(fingerprint)) {
805+
result.push(fingerprint)
806+
}
807+
}
808+
return result
809+
}
810+
811+
812+
public getExpectedSigners = async (proposal: { policy_path?: PolicyPath, descriptor: string }, signers: PublishedOwnedSigner[]): Promise<string[]> => {
813+
const policyTree = this.getPolicy()
814+
const policyPath = proposal.policy_path
815+
const ownedFingerprints = signers.map(signer => signer.fingerprint)
816+
if (!policyPath) {
817+
return this.searchSignerInDescriptor(ownedFingerprints, proposal.descriptor)
818+
}
819+
const numOfExpectedConditions = Object.values(policyPath).reduce((acc, arr) => acc + arr.length, 0)
820+
let current = policyTree
821+
const pending = [current]
822+
const conditions = [current]
823+
let currentPendingIndex = 0
824+
const fingerprints = new Set<string>()
825+
while (conditions.length < numOfExpectedConditions) {
826+
let current = pending[currentPendingIndex]
827+
const indices = policyPath[current.get('id') as string]
828+
const maybeItems = current.get('items')
829+
if (!maybeItems) {
830+
conditions.push(current)
831+
if (current.has('fingerprint')) {
832+
fingerprints.add(current.get('fingerprint'))
833+
}
834+
} else {
835+
for (const index of indices) {
836+
const item = current.get('items')[index]
837+
conditions.push(item)
838+
if (policyPath[item.get('id')]) {
839+
pending.push(item)
840+
}
841+
this.addFingerprints(item, policyPath, fingerprints)
842+
}
843+
}
844+
currentPendingIndex++
845+
}
846+
return Array.from(fingerprints).filter(fingerprint => ownedFingerprints.includes(fingerprint))
847+
}
780848
}

src/models/interfaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseOwnedSigner, Trx, FinalizeTrxResponse, UndecoratedBasicTrxDetails, UndecoratedTrxDetails, Utxo, PolicyPathSelector, PolicyPathsResult, Address } from "./types"
2-
import { PsbtObject } from "../types"
2+
import { PsbtObject, PolicyPath } from "../types"
33

44
type BalancePayload = {
55
confirmed: number
@@ -26,7 +26,7 @@ export interface Wallet {
2626
* @param {Map<string,Array<number>>} policy_path
2727
* @returns {Promise<any>}
2828
*/
29-
build_trx(address: string, amount: string, fee_rate: string, policy_path?: Map<string, Array<number>>, utxos?: Array<string>, frozen_utxos?: Array<string>): Promise<Trx>;
29+
build_trx(address: string, amount: string, fee_rate: string, policy_path?: PolicyPath, utxos?: Array<string>, frozen_utxos?: Array<string>): Promise<Trx>;
3030

3131
/**
3232
* @returns {Map<string, any>}

0 commit comments

Comments
 (0)