@bsv/sdk 1.4.0 → 1.4.1

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.
Files changed (58) hide show
  1. package/dist/cjs/mod.js +2 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/src/identity/IdentityClient.js +258 -0
  5. package/dist/cjs/src/identity/IdentityClient.js.map +1 -0
  6. package/dist/cjs/src/identity/index.js +19 -0
  7. package/dist/cjs/src/identity/index.js.map +1 -0
  8. package/dist/cjs/src/identity/types/index.js +30 -0
  9. package/dist/cjs/src/identity/types/index.js.map +1 -0
  10. package/dist/cjs/src/registry/RegistryClient.js +392 -0
  11. package/dist/cjs/src/registry/RegistryClient.js.map +1 -0
  12. package/dist/cjs/src/registry/index.js +19 -0
  13. package/dist/cjs/src/registry/index.js.map +1 -0
  14. package/dist/cjs/src/registry/types/index.js +3 -0
  15. package/dist/cjs/src/registry/types/index.js.map +1 -0
  16. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/mod.js +2 -0
  18. package/dist/esm/mod.js.map +1 -1
  19. package/dist/esm/src/identity/IdentityClient.js +255 -0
  20. package/dist/esm/src/identity/IdentityClient.js.map +1 -0
  21. package/dist/esm/src/identity/index.js +3 -0
  22. package/dist/esm/src/identity/index.js.map +1 -0
  23. package/dist/esm/src/identity/types/index.js +27 -0
  24. package/dist/esm/src/identity/types/index.js.map +1 -0
  25. package/dist/esm/src/registry/RegistryClient.js +388 -0
  26. package/dist/esm/src/registry/RegistryClient.js.map +1 -0
  27. package/dist/esm/src/registry/index.js +3 -0
  28. package/dist/esm/src/registry/index.js.map +1 -0
  29. package/dist/esm/src/registry/types/index.js +2 -0
  30. package/dist/esm/src/registry/types/index.js.map +1 -0
  31. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  32. package/dist/types/mod.d.ts +2 -0
  33. package/dist/types/mod.d.ts.map +1 -1
  34. package/dist/types/src/identity/IdentityClient.d.ts +50 -0
  35. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -0
  36. package/dist/types/src/identity/index.d.ts +3 -0
  37. package/dist/types/src/identity/index.d.ts.map +1 -0
  38. package/dist/types/src/identity/types/index.d.ts +30 -0
  39. package/dist/types/src/identity/types/index.d.ts.map +1 -0
  40. package/dist/types/src/registry/RegistryClient.d.ts +94 -0
  41. package/dist/types/src/registry/RegistryClient.d.ts.map +1 -0
  42. package/dist/types/src/registry/index.d.ts +3 -0
  43. package/dist/types/src/registry/index.d.ts.map +1 -0
  44. package/dist/types/src/registry/types/index.d.ts +86 -0
  45. package/dist/types/src/registry/types/index.d.ts.map +1 -0
  46. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  47. package/dist/umd/bundle.js +1 -1
  48. package/mod.ts +3 -1
  49. package/package.json +2 -2
  50. package/src/identity/IdentityClient.ts +305 -0
  51. package/src/identity/README.md +93 -0
  52. package/src/identity/__tests/IdentityClient.test.ts +278 -0
  53. package/src/identity/index.ts +2 -0
  54. package/src/identity/types/index.ts +46 -0
  55. package/src/registry/RegistryClient.ts +493 -0
  56. package/src/registry/__tests/RegistryClient.test.ts +444 -0
  57. package/src/registry/index.ts +2 -0
  58. package/src/registry/types/index.ts +101 -0
@@ -0,0 +1,278 @@
1
+ import { WalletCertificate, WalletInterface } from '../../wallet/index'
2
+ import { IdentityClient } from '../IdentityClient'
3
+ import { Certificate } from '../../auth/certificates/index.js'
4
+ import { KNOWN_IDENTITY_TYPES, defaultIdentity } from '../types/index.js'
5
+
6
+ // ----- Mocks for external dependencies -----
7
+ jest.mock('../../script', () => {
8
+ return {
9
+ PushDrop: jest.fn().mockImplementation(() => ({
10
+ lock: jest.fn().mockResolvedValue({
11
+ toHex: () => 'lockingScriptHex'
12
+ }),
13
+ unlock: jest.fn()
14
+ }))
15
+ }
16
+ })
17
+
18
+ jest.mock('../../overlay-tools/index.js', () => {
19
+ return {
20
+ TopicBroadcaster: jest.fn().mockImplementation(() => ({
21
+ broadcast: jest.fn().mockResolvedValue('broadcastResult')
22
+ }))
23
+ }
24
+ })
25
+
26
+ jest.mock('../../transaction/index.js', () => {
27
+ return {
28
+ Transaction: {
29
+ fromAtomicBEEF: jest.fn().mockImplementation((tx) => ({
30
+ toHexBEEF: () => 'transactionHex'
31
+ })),
32
+ fromBEEF: jest.fn()
33
+ }
34
+ }
35
+ })
36
+
37
+ // ----- Begin Test Suite -----
38
+ describe('IdentityClient', () => {
39
+ let walletMock: Partial<WalletInterface>
40
+ let identityClient: IdentityClient
41
+
42
+ beforeEach(() => {
43
+ // Create a fake wallet implementing the methods used by IdentityClient.
44
+ walletMock = {
45
+ proveCertificate: jest.fn().mockResolvedValue({ keyringForVerifier: 'fakeKeyring' }),
46
+ createAction: jest.fn().mockResolvedValue({
47
+ tx: [1, 2, 3],
48
+ signableTransaction: { tx: [1, 2, 3], reference: 'ref' }
49
+ }),
50
+ listCertificates: jest.fn().mockResolvedValue({ certificates: [] }),
51
+ acquireCertificate: jest.fn().mockResolvedValue({
52
+ fields: { name: 'Alice' },
53
+ verify: jest.fn().mockResolvedValue(true)
54
+ }),
55
+ signAction: jest.fn().mockResolvedValue({ tx: [4, 5, 6] }),
56
+ getNetwork: jest.fn().mockResolvedValue({ network: 'testnet' }),
57
+ discoverByIdentityKey: jest.fn(),
58
+ discoverByAttributes: jest.fn()
59
+ }
60
+
61
+ identityClient = new IdentityClient(walletMock as WalletInterface)
62
+
63
+ // Clear any previous calls/spies.
64
+ jest.clearAllMocks()
65
+ })
66
+
67
+ describe('publiclyRevealAttributes', () => {
68
+ it('should throw an error if certificate has no fields', async () => {
69
+ const certificate = {
70
+ fields: {},
71
+ verify: jest.fn().mockResolvedValue(true)
72
+ } as any as WalletCertificate
73
+ const fieldsToReveal = ['name']
74
+ await expect(
75
+ identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
76
+ ).rejects.toThrow('Certificate has no fields to reveal!')
77
+ })
78
+
79
+ it('should throw an error if fieldsToReveal is empty', async () => {
80
+ const certificate = {
81
+ fields: { name: 'Alice' },
82
+ verify: jest.fn().mockResolvedValue(true)
83
+ } as any as WalletCertificate
84
+ const fieldsToReveal: string[] = []
85
+ await expect(
86
+ identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
87
+ ).rejects.toThrow('You must reveal at least one field!')
88
+ })
89
+
90
+ it('should throw an error if certificate verification fails', async () => {
91
+ const certificate = {
92
+ fields: { name: 'Alice' },
93
+ verify: jest.fn().mockRejectedValue(new Error('Verification error')),
94
+ type: 'dummyType',
95
+ serialNumber: 'dummySerial',
96
+ subject: 'dummySubject',
97
+ certifier: 'dummyCertifier',
98
+ revocationOutpoint: 'dummyRevocation',
99
+ signature: 'dummySignature'
100
+ } as any as WalletCertificate
101
+ const fieldsToReveal = ['name']
102
+ await expect(
103
+ identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
104
+ ).rejects.toThrow('Certificate verification failed!')
105
+ })
106
+
107
+ it('should publicly reveal attributes successfully', async () => {
108
+ // Prepare a dummy certificate with all required properties.
109
+ const certificate = {
110
+ fields: { name: 'Alice' },
111
+ verify: jest.fn().mockResolvedValue(true), // this property is not used since the Certificate is re-instantiated
112
+ type: 'xCert',
113
+ serialNumber: '12345',
114
+ subject: 'abcdef1234567890',
115
+ certifier: 'CertifierX',
116
+ revocationOutpoint: 'outpoint1',
117
+ signature: 'signature1'
118
+ } as any as WalletCertificate
119
+
120
+ // Ensure that Certificate.verify (called on the re-instantiated Certificate)
121
+ // resolves successfully.
122
+ jest.spyOn(Certificate.prototype, 'verify').mockResolvedValue(false)
123
+
124
+ const fieldsToReveal = ['name']
125
+ const result = await identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
126
+ expect(result).toEqual('broadcastResult')
127
+
128
+ // Validate that proveCertificate was called with the proper arguments.
129
+ expect(walletMock.proveCertificate).toHaveBeenCalledWith({
130
+ certificate,
131
+ fieldsToReveal,
132
+ verifier: expect.any(String)
133
+ })
134
+
135
+ // Validate that createAction was called.
136
+ expect(walletMock.createAction).toHaveBeenCalled()
137
+ })
138
+ })
139
+
140
+ describe('resolveByIdentityKey', () => {
141
+ it('should return parsed identities from discovered certificates', async () => {
142
+ const dummyCertificate = {
143
+ type: KNOWN_IDENTITY_TYPES.xCert,
144
+ subject: 'abcdef1234567890',
145
+ decryptedFields: {
146
+ userName: 'Alice',
147
+ profilePhoto: 'alicePhotoUrl'
148
+ },
149
+ certifierInfo: {
150
+ name: 'CertifierX',
151
+ iconUrl: 'certifierIconUrl'
152
+ }
153
+ }
154
+ // Mock discoverByIdentityKey to return a certificate list.
155
+ walletMock.discoverByIdentityKey = jest.fn().mockResolvedValue({ certificates: [dummyCertificate] })
156
+
157
+ const identities = await identityClient.resolveByIdentityKey({ identityKey: 'dummyKey' })
158
+ expect(walletMock.discoverByIdentityKey).toHaveBeenCalledWith({ identityKey: 'dummyKey' }, undefined)
159
+ expect(identities).toHaveLength(1)
160
+ expect(identities[0]).toEqual({
161
+ name: 'Alice',
162
+ avatarURL: 'alicePhotoUrl',
163
+ abbreviatedKey: 'abcdef1234...',
164
+ identityKey: 'abcdef1234567890',
165
+ badgeLabel: 'X account certified by CertifierX',
166
+ badgeIconURL: 'certifierIconUrl',
167
+ badgeClickURL: 'https://socialcert.net'
168
+ })
169
+ })
170
+ })
171
+
172
+ it('should throw if createAction returns no tx', async () => {
173
+ const certificate = {
174
+ fields: { name: 'Alice' },
175
+ verify: jest.fn().mockResolvedValue(true),
176
+ type: 'xCert',
177
+ serialNumber: '12345',
178
+ subject: 'abcdef1234567890',
179
+ certifier: 'CertifierX',
180
+ revocationOutpoint: 'outpoint1',
181
+ signature: 'signature1'
182
+ } as any as WalletCertificate
183
+
184
+ jest.spyOn(Certificate.prototype, 'verify').mockResolvedValue(false)
185
+
186
+ // Simulate createAction returning an object with tx = undefined
187
+ walletMock.createAction = jest.fn().mockResolvedValue({
188
+ tx: undefined,
189
+ signableTransaction: { tx: undefined, reference: 'ref' }
190
+ })
191
+
192
+ const fieldsToReveal = ['name']
193
+
194
+ await expect(
195
+ identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
196
+ ).rejects.toThrow('Public reveal failed: failed to create action!')
197
+ })
198
+
199
+ describe('resolveByAttributes', () => {
200
+ it('should return parsed identities from discovered certificates', async () => {
201
+ const dummyCertificate = {
202
+ type: KNOWN_IDENTITY_TYPES.emailCert,
203
+ subject: 'emailSubject1234',
204
+ decryptedFields: {
205
+ email: 'alice@example.com',
206
+ profilePhoto: 'ignored' // not used for email type
207
+ },
208
+ certifierInfo: {
209
+ name: 'EmailCertifier',
210
+ iconUrl: 'emailIconUrl'
211
+ }
212
+ }
213
+ // Mock discoverByAttributes to return a certificate list.
214
+ walletMock.discoverByAttributes = jest.fn().mockResolvedValue({ certificates: [dummyCertificate] })
215
+
216
+ const identities = await identityClient.resolveByAttributes({ attributes: { email: 'alice@example.com' } })
217
+ expect(walletMock.discoverByAttributes).toHaveBeenCalledWith({ attributes: { email: 'alice@example.com' } }, undefined)
218
+ expect(identities).toHaveLength(1)
219
+ expect(identities[0]).toEqual({
220
+ name: 'alice@example.com',
221
+ avatarURL: 'XUTZxep7BBghAJbSBwTjNfmcsDdRFs5EaGEgkESGSgjJVYgMEizu',
222
+ abbreviatedKey: 'emailSubje...',
223
+ identityKey: 'emailSubject1234',
224
+ badgeLabel: 'Email certified by EmailCertifier',
225
+ badgeIconURL: 'emailIconUrl',
226
+ badgeClickURL: 'https://socialcert.net'
227
+ })
228
+ })
229
+ })
230
+
231
+ describe('parseIdentity', () => {
232
+ it('should correctly parse an xCert identity', () => {
233
+ const dummyCertificate = {
234
+ type: KNOWN_IDENTITY_TYPES.xCert,
235
+ subject: 'abcdef1234567890',
236
+ decryptedFields: {
237
+ userName: 'Alice',
238
+ profilePhoto: 'alicePhotoUrl'
239
+ },
240
+ certifierInfo: {
241
+ name: 'CertifierX',
242
+ iconUrl: 'certifierIconUrl'
243
+ }
244
+ }
245
+ const identity = IdentityClient.parseIdentity(dummyCertificate as unknown as any)
246
+ expect(identity).toEqual({
247
+ name: 'Alice',
248
+ avatarURL: 'alicePhotoUrl',
249
+ abbreviatedKey: 'abcdef1234...',
250
+ identityKey: 'abcdef1234567890',
251
+ badgeLabel: 'X account certified by CertifierX',
252
+ badgeIconURL: 'certifierIconUrl',
253
+ badgeClickURL: 'https://socialcert.net'
254
+ })
255
+ })
256
+
257
+ it('should return default identity for unknown type', () => {
258
+ const dummyCertificate = {
259
+ type: 'unknownType',
260
+ subject: '',
261
+ decryptedFields: {
262
+ profilePhoto: 'defaultPhoto'
263
+ },
264
+ certifierInfo: {}
265
+ }
266
+ const identity = IdentityClient.parseIdentity(dummyCertificate as any)
267
+ expect(identity).toEqual({
268
+ name: defaultIdentity.name,
269
+ avatarURL: 'defaultPhoto',
270
+ abbreviatedKey: '',
271
+ identityKey: '',
272
+ badgeLabel: defaultIdentity.badgeLabel,
273
+ badgeIconURL: defaultIdentity.badgeIconURL,
274
+ badgeClickURL: defaultIdentity.badgeClickURL
275
+ })
276
+ })
277
+ })
278
+ })
@@ -0,0 +1,2 @@
1
+ export * from './IdentityClient.js'
2
+ export * from './types/index.js'
@@ -0,0 +1,46 @@
1
+ import { WalletProtocol } from "../../wallet/index.js"
2
+
3
+ export const defaultIdentity: DisplayableIdentity = {
4
+ name: 'Unknown Identity',
5
+ avatarURL: 'XUUB8bbn9fEthk15Ge3zTQXypUShfC94vFjp65v7u5CQ8qkpxzst',
6
+ identityKey: '',
7
+ abbreviatedKey: '',
8
+ badgeIconURL: 'XUUV39HVPkpmMzYNTx7rpKzJvXfeiVyQWg2vfSpjBAuhunTCA9uG',
9
+ badgeLabel: 'Not verified by anyone you trust.',
10
+ badgeClickURL: 'https://projectbabbage.com/docs/unknown-identity'
11
+ }
12
+
13
+ export interface IdentityClientOptions {
14
+ protocolID: WalletProtocol
15
+ keyID: string
16
+ tokenAmount: number
17
+ outputIndex: number
18
+ }
19
+ export const DEFAULT_IDENTITY_CLIENT_OPTIONS: IdentityClientOptions = {
20
+ protocolID: [1, 'identity'],
21
+ keyID: '1',
22
+ tokenAmount: 1,
23
+ outputIndex: 0
24
+ }
25
+
26
+ export interface DisplayableIdentity {
27
+ name: string
28
+ avatarURL: string
29
+ abbreviatedKey: string
30
+ identityKey: string
31
+ badgeIconURL: string
32
+ badgeLabel: string
33
+ badgeClickURL: string
34
+ }
35
+
36
+ export const KNOWN_IDENTITY_TYPES = {
37
+ identiCert: 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY=',
38
+ discordCert: '2TgqRC35B1zehGmB21xveZNc7i5iqHc0uxMb+1NMPW4=',
39
+ phoneCert: 'mffUklUzxbHr65xLohn0hRL0Tq2GjW1GYF/OPfzqJ6A=',
40
+ xCert: 'vdDWvftf1H+5+ZprUw123kjHlywH+v20aPQTuXgMpNc=',
41
+ registrant: 'YoPsbfR6YQczjzPdHCoGC7nJsOdPQR50+SYqcWpJ0y0=',
42
+ emailCert: 'exOl3KM0dIJ04EW5pZgbZmPag6MdJXd3/a1enmUU/BA=',
43
+ anyone: 'mfkOMfLDQmrr3SBxBQ5WeE+6Hy3VJRFq6w4A5Ljtlis=',
44
+ self: 'Hkge6X5JRxt1cWXtHLCrSTg6dCVTxjQJJ48iOYd7n3g=',
45
+ coolCert: 'AGfk/WrT1eBDXpz3mcw386Zww2HmqcIn3uY6x4Af1eo='
46
+ }