@bsv/wallet-toolbox 1.6.24 → 1.6.25
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/mobile/out/src/WalletPermissionsManager.d.ts +3 -0
- package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.js +185 -43
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/package-lock.json +2 -2
- package/mobile/package.json +1 -1
- package/out/src/WalletPermissionsManager.d.ts +3 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +185 -43
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/WalletPermissionsManager.ts +245 -57
package/package.json
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
WalletProtocol,
|
|
8
8
|
Base64String,
|
|
9
9
|
PubKeyHex,
|
|
10
|
-
SecurityLevels
|
|
10
|
+
SecurityLevels,
|
|
11
|
+
CreateActionInput,
|
|
12
|
+
Beef
|
|
11
13
|
} from '@bsv/sdk'
|
|
12
14
|
import { validateCreateActionArgs } from './sdk'
|
|
13
15
|
|
|
@@ -656,17 +658,39 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
656
658
|
)
|
|
657
659
|
}
|
|
658
660
|
for (const p of params.granted.protocolPermissions || []) {
|
|
659
|
-
await this.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
counterparty: p.counterparty || 'self',
|
|
666
|
-
reason: p.description
|
|
667
|
-
},
|
|
668
|
-
expiry
|
|
661
|
+
const token = await this.findProtocolToken(
|
|
662
|
+
originator,
|
|
663
|
+
false, // No privileged protocols allowed in groups for added security.
|
|
664
|
+
p.protocolID,
|
|
665
|
+
p.counterparty || 'self',
|
|
666
|
+
true
|
|
669
667
|
)
|
|
668
|
+
if (token) {
|
|
669
|
+
await this.renewPermissionOnChain(
|
|
670
|
+
token,
|
|
671
|
+
{
|
|
672
|
+
type: 'protocol',
|
|
673
|
+
originator,
|
|
674
|
+
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
675
|
+
protocolID: p.protocolID,
|
|
676
|
+
counterparty: p.counterparty || 'self',
|
|
677
|
+
reason: p.description
|
|
678
|
+
},
|
|
679
|
+
expiry
|
|
680
|
+
)
|
|
681
|
+
} else {
|
|
682
|
+
await this.createPermissionOnChain(
|
|
683
|
+
{
|
|
684
|
+
type: 'protocol',
|
|
685
|
+
originator,
|
|
686
|
+
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
687
|
+
protocolID: p.protocolID,
|
|
688
|
+
counterparty: p.counterparty || 'self',
|
|
689
|
+
reason: p.description
|
|
690
|
+
},
|
|
691
|
+
expiry
|
|
692
|
+
)
|
|
693
|
+
}
|
|
670
694
|
}
|
|
671
695
|
for (const b of params.granted.basketAccess || []) {
|
|
672
696
|
await this.createPermissionOnChain(
|
|
@@ -1335,6 +1359,89 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
1335
1359
|
return undefined
|
|
1336
1360
|
}
|
|
1337
1361
|
|
|
1362
|
+
/** Finds ALL DPACP permission tokens matching origin/domain, privileged, protocol, cpty. Never filters by expiry. */
|
|
1363
|
+
private async findAllProtocolTokens(
|
|
1364
|
+
originator: string,
|
|
1365
|
+
privileged: boolean,
|
|
1366
|
+
protocolID: WalletProtocol,
|
|
1367
|
+
counterparty: string
|
|
1368
|
+
): Promise<PermissionToken[]> {
|
|
1369
|
+
const [secLevel, protoName] = protocolID
|
|
1370
|
+
const tags = [
|
|
1371
|
+
`originator ${originator}`,
|
|
1372
|
+
`privileged ${!!privileged}`,
|
|
1373
|
+
`protocolName ${protoName}`,
|
|
1374
|
+
`protocolSecurityLevel ${secLevel}`
|
|
1375
|
+
]
|
|
1376
|
+
if (secLevel === 2) {
|
|
1377
|
+
tags.push(`counterparty ${counterparty}`)
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const result = await this.underlying.listOutputs(
|
|
1381
|
+
{
|
|
1382
|
+
basket: BASKET_MAP.protocol,
|
|
1383
|
+
tags,
|
|
1384
|
+
tagQueryMode: 'all',
|
|
1385
|
+
include: 'entire transactions'
|
|
1386
|
+
},
|
|
1387
|
+
this.adminOriginator
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1390
|
+
const matches: PermissionToken[] = []
|
|
1391
|
+
|
|
1392
|
+
for (const out of result.outputs) {
|
|
1393
|
+
const [txid, outputIndexStr] = out.outpoint.split('.')
|
|
1394
|
+
const tx = Transaction.fromBEEF(result.BEEF!, txid)
|
|
1395
|
+
const vout = Number(outputIndexStr)
|
|
1396
|
+
const dec = PushDrop.decode(tx.outputs[vout].lockingScript)
|
|
1397
|
+
if (!dec || !dec.fields || dec.fields.length < 6) continue
|
|
1398
|
+
|
|
1399
|
+
const domainRaw = dec.fields[0]
|
|
1400
|
+
const expiryRaw = dec.fields[1]
|
|
1401
|
+
const privRaw = dec.fields[2]
|
|
1402
|
+
const secLevelRaw = dec.fields[3]
|
|
1403
|
+
const protoNameRaw = dec.fields[4]
|
|
1404
|
+
const counterpartyRaw = dec.fields[5]
|
|
1405
|
+
|
|
1406
|
+
// Decrypt all fields
|
|
1407
|
+
const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
|
|
1408
|
+
const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
|
|
1409
|
+
const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
|
|
1410
|
+
const secLevelDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10) as
|
|
1411
|
+
| 0
|
|
1412
|
+
| 1
|
|
1413
|
+
| 2
|
|
1414
|
+
const protoNameDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw))
|
|
1415
|
+
const cptyDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw))
|
|
1416
|
+
|
|
1417
|
+
// Strict attribute match; NO expiry filtering
|
|
1418
|
+
if (
|
|
1419
|
+
domainDecoded !== originator ||
|
|
1420
|
+
privDecoded !== !!privileged ||
|
|
1421
|
+
secLevelDecoded !== secLevel ||
|
|
1422
|
+
protoNameDecoded !== protoName ||
|
|
1423
|
+
(secLevelDecoded === 2 && cptyDecoded !== counterparty)
|
|
1424
|
+
) {
|
|
1425
|
+
continue
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
matches.push({
|
|
1429
|
+
tx: tx.toBEEF(),
|
|
1430
|
+
txid,
|
|
1431
|
+
outputIndex: vout,
|
|
1432
|
+
outputScript: tx.outputs[vout].lockingScript.toHex(),
|
|
1433
|
+
satoshis: out.satoshis,
|
|
1434
|
+
originator,
|
|
1435
|
+
privileged,
|
|
1436
|
+
protocol: protoName,
|
|
1437
|
+
securityLevel: secLevel,
|
|
1438
|
+
expiry: expiryDecoded,
|
|
1439
|
+
counterparty: cptyDecoded
|
|
1440
|
+
})
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
return matches
|
|
1444
|
+
}
|
|
1338
1445
|
/** Looks for a DBAP token matching (originator, basket). */
|
|
1339
1446
|
private async findBasketToken(
|
|
1340
1447
|
originator: string,
|
|
@@ -1569,6 +1676,70 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
1569
1676
|
)
|
|
1570
1677
|
}
|
|
1571
1678
|
|
|
1679
|
+
private async coalescePermissionTokens(
|
|
1680
|
+
oldTokens: PermissionToken[],
|
|
1681
|
+
newScript: LockingScript,
|
|
1682
|
+
opts?: {
|
|
1683
|
+
tags?: string[]
|
|
1684
|
+
basket?: string
|
|
1685
|
+
description?: string
|
|
1686
|
+
}
|
|
1687
|
+
): Promise<string> {
|
|
1688
|
+
if (!oldTokens?.length) throw new Error('No permission tokens to coalesce')
|
|
1689
|
+
if (oldTokens.length < 2) throw new Error('Need at least 2 tokens to coalesce')
|
|
1690
|
+
|
|
1691
|
+
// 1) Create a signable action with N inputs and a single renewed output
|
|
1692
|
+
const { signableTransaction } = await this.createAction(
|
|
1693
|
+
{
|
|
1694
|
+
description: opts?.description ?? `Coalesce ${oldTokens.length} permission tokens`,
|
|
1695
|
+
inputs: oldTokens.map((t, i) => ({
|
|
1696
|
+
outpoint: `${t.txid}.${t.outputIndex}`,
|
|
1697
|
+
unlockingScriptLength: 74,
|
|
1698
|
+
inputDescription: `Consume permission token #${i + 1}`
|
|
1699
|
+
})),
|
|
1700
|
+
outputs: [
|
|
1701
|
+
{
|
|
1702
|
+
lockingScript: newScript.toHex(),
|
|
1703
|
+
satoshis: 1,
|
|
1704
|
+
outputDescription: 'Renewed permission token',
|
|
1705
|
+
...(opts?.basket ? { basket: opts.basket } : {}),
|
|
1706
|
+
...(opts?.tags ? { tags: opts.tags } : {})
|
|
1707
|
+
}
|
|
1708
|
+
],
|
|
1709
|
+
options: {
|
|
1710
|
+
acceptDelayedBroadcast: false,
|
|
1711
|
+
randomizeOutputs: false,
|
|
1712
|
+
signAndProcess: false
|
|
1713
|
+
}
|
|
1714
|
+
},
|
|
1715
|
+
this.adminOriginator
|
|
1716
|
+
)
|
|
1717
|
+
|
|
1718
|
+
if (!signableTransaction?.reference || !signableTransaction.tx) {
|
|
1719
|
+
throw new Error('Failed to create signable transaction')
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// 2) Sign each input
|
|
1723
|
+
const partialTx = Transaction.fromAtomicBEEF(signableTransaction.tx)
|
|
1724
|
+
const pushdrop = new PushDrop(this.underlying)
|
|
1725
|
+
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self')
|
|
1726
|
+
|
|
1727
|
+
const spends: Record<number, { unlockingScript: string }> = {}
|
|
1728
|
+
for (let i = 0; i < oldTokens.length; i++) {
|
|
1729
|
+
// The signable transaction already contains the necessary prevout context
|
|
1730
|
+
const unlockingScript = await unlocker.sign(partialTx, i)
|
|
1731
|
+
spends[i] = { unlockingScript: unlockingScript.toHex() }
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// 3) Finalize the action
|
|
1735
|
+
const { txid } = await this.underlying.signAction({
|
|
1736
|
+
reference: signableTransaction.reference,
|
|
1737
|
+
spends
|
|
1738
|
+
})
|
|
1739
|
+
|
|
1740
|
+
if (!txid) throw new Error('Failed to finalize coalescing transaction')
|
|
1741
|
+
return txid
|
|
1742
|
+
}
|
|
1572
1743
|
/**
|
|
1573
1744
|
* Renews a permission token by spending the old token as input and creating a new token output.
|
|
1574
1745
|
* This invalidates the old token and replaces it with a new one.
|
|
@@ -1596,57 +1767,74 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
1596
1767
|
true,
|
|
1597
1768
|
true
|
|
1598
1769
|
)
|
|
1599
|
-
|
|
1600
1770
|
const tags = this.buildTagsForRequest(r)
|
|
1771
|
+
// Check if there are multiple old tokens for the same parameters (shouldn't usually happen)
|
|
1772
|
+
const oldTokens = await this.findAllProtocolTokens(
|
|
1773
|
+
oldToken.originator,
|
|
1774
|
+
oldToken.privileged!,
|
|
1775
|
+
[oldToken.securityLevel!, oldToken.protocol!],
|
|
1776
|
+
oldToken.counterparty!
|
|
1777
|
+
)
|
|
1601
1778
|
|
|
1602
|
-
//
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
description: `
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1779
|
+
// If so, coalesce them into a single token first, to avoid bloat
|
|
1780
|
+
if (oldTokens.length > 1) {
|
|
1781
|
+
const txid = await this.coalescePermissionTokens(oldTokens, newScript, {
|
|
1782
|
+
tags,
|
|
1783
|
+
basket: BASKET_MAP[r.type],
|
|
1784
|
+
description: `Coalesce ${r.type} permission tokens`
|
|
1785
|
+
})
|
|
1786
|
+
console.log('Coalesced permission tokens:', txid)
|
|
1787
|
+
} else {
|
|
1788
|
+
// Otherwise, just proceed with the single-token renewal
|
|
1789
|
+
// 3) For BRC-100, we do a "createAction" with a partial input referencing oldToken
|
|
1790
|
+
// plus a single new output. We'll hydrate the template, then signAction for the wallet to finalize.
|
|
1791
|
+
const oldOutpoint = `${oldToken.txid}.${oldToken.outputIndex}`
|
|
1792
|
+
const { signableTransaction } = await this.createAction(
|
|
1793
|
+
{
|
|
1794
|
+
description: `Renew ${r.type} permission`,
|
|
1795
|
+
inputBEEF: oldToken.tx,
|
|
1796
|
+
inputs: [
|
|
1797
|
+
{
|
|
1798
|
+
outpoint: oldOutpoint,
|
|
1799
|
+
unlockingScriptLength: 73, // length of signature
|
|
1800
|
+
inputDescription: `Consume old ${r.type} token`
|
|
1801
|
+
}
|
|
1802
|
+
],
|
|
1803
|
+
outputs: [
|
|
1804
|
+
{
|
|
1805
|
+
lockingScript: newScript.toHex(),
|
|
1806
|
+
satoshis: 1,
|
|
1807
|
+
outputDescription: `Renewed ${r.type} permission token`,
|
|
1808
|
+
basket: BASKET_MAP[r.type],
|
|
1809
|
+
tags
|
|
1810
|
+
}
|
|
1811
|
+
],
|
|
1812
|
+
options: {
|
|
1813
|
+
acceptDelayedBroadcast: false
|
|
1614
1814
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1815
|
+
},
|
|
1816
|
+
this.adminOriginator
|
|
1817
|
+
)
|
|
1818
|
+
const tx = Transaction.fromBEEF(signableTransaction!.tx)
|
|
1819
|
+
const unlocker = new PushDrop(this.underlying).unlock(
|
|
1820
|
+
WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
1821
|
+
'1',
|
|
1822
|
+
'self',
|
|
1823
|
+
'all',
|
|
1824
|
+
false,
|
|
1825
|
+
1,
|
|
1826
|
+
LockingScript.fromHex(oldToken.outputScript)
|
|
1827
|
+
)
|
|
1828
|
+
const unlockingScript = await unlocker.sign(tx, 0)
|
|
1829
|
+
await this.underlying.signAction({
|
|
1830
|
+
reference: signableTransaction!.reference,
|
|
1831
|
+
spends: {
|
|
1832
|
+
0: {
|
|
1833
|
+
unlockingScript: unlockingScript.toHex()
|
|
1623
1834
|
}
|
|
1624
|
-
],
|
|
1625
|
-
options: {
|
|
1626
|
-
acceptDelayedBroadcast: false
|
|
1627
|
-
}
|
|
1628
|
-
},
|
|
1629
|
-
this.adminOriginator
|
|
1630
|
-
)
|
|
1631
|
-
const tx = Transaction.fromBEEF(signableTransaction!.tx)
|
|
1632
|
-
const unlocker = new PushDrop(this.underlying).unlock(
|
|
1633
|
-
WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
1634
|
-
'1',
|
|
1635
|
-
'self',
|
|
1636
|
-
'all',
|
|
1637
|
-
false,
|
|
1638
|
-
1,
|
|
1639
|
-
LockingScript.fromHex(oldToken.outputScript)
|
|
1640
|
-
)
|
|
1641
|
-
const unlockingScript = await unlocker.sign(tx, 0)
|
|
1642
|
-
await this.underlying.signAction({
|
|
1643
|
-
reference: signableTransaction!.reference,
|
|
1644
|
-
spends: {
|
|
1645
|
-
0: {
|
|
1646
|
-
unlockingScript: unlockingScript.toHex()
|
|
1647
1835
|
}
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1836
|
+
})
|
|
1837
|
+
}
|
|
1650
1838
|
}
|
|
1651
1839
|
|
|
1652
1840
|
/**
|