@audius/sdk 2.0.3-beta.6 → 2.0.3-beta.7

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 (39) hide show
  1. package/dist/api/Account.d.ts +26 -0
  2. package/dist/api/Users.d.ts +20 -0
  3. package/dist/core.js +5 -0
  4. package/dist/core.js.map +1 -1
  5. package/dist/index.cjs.js +2658 -932
  6. package/dist/index.cjs.js.map +1 -1
  7. package/dist/index.esm.js +2646 -920
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/legacy.js +2656 -929
  10. package/dist/legacy.js.map +1 -1
  11. package/dist/native-libs.js +2647 -921
  12. package/dist/native-libs.js.map +1 -1
  13. package/dist/services/creatorNode/CreatorNode.d.ts +3 -7
  14. package/dist/services/dataContracts/IPLDBlacklistFactoryClient.d.ts +17 -0
  15. package/dist/services/dataContracts/PlaylistFactoryClient.d.ts +22 -0
  16. package/dist/services/dataContracts/RegistryClient.d.ts +15 -0
  17. package/dist/services/dataContracts/SocialFeatureFactoryClient.d.ts +11 -0
  18. package/dist/services/dataContracts/TrackFactoryClient.d.ts +23 -0
  19. package/dist/services/dataContracts/UserFactoryClient.d.ts +64 -0
  20. package/dist/services/dataContracts/UserLibraryFactoryClient.d.ts +9 -0
  21. package/dist/services/dataContracts/UserReplicaSetManagerClient.d.ts +82 -0
  22. package/dist/services/discoveryProvider/DiscoveryProvider.d.ts +1 -1
  23. package/dist/services/discoveryProvider/requests.d.ts +2 -1
  24. package/dist/services/solanaAudiusData/SolanaAudiusData.d.ts +408 -0
  25. package/dist/services/solanaAudiusData/errors.d.ts +7 -0
  26. package/dist/services/solanaAudiusData/index.d.ts +2 -0
  27. package/dist/utils/fileHasher.d.ts +9 -2
  28. package/dist/utils/types.d.ts +1 -0
  29. package/dist/utils/utils.d.ts +1 -0
  30. package/package.json +2 -1
  31. package/src/api/Account.ts +77 -0
  32. package/src/api/Users.ts +181 -3
  33. package/src/eth-contracts/ABIs/Wormhole.json +121 -11
  34. package/src/services/creatorNode/CreatorNode.ts +9 -0
  35. package/src/services/discoveryProvider/DiscoveryProvider.ts +4 -2
  36. package/src/services/discoveryProvider/requests.ts +6 -1
  37. package/src/services/solana/rewardsAttester.ts +47 -21
  38. package/src/utils/fileHasher.ts +18 -3
  39. package/src/utils/types.ts +1 -0
package/src/api/Users.ts CHANGED
@@ -68,6 +68,12 @@ export class Users extends Base {
68
68
  this.getUserSubscribers = this.getUserSubscribers.bind(this)
69
69
  this.bulkGetUserSubscribers = this.bulkGetUserSubscribers.bind(this)
70
70
 
71
+ this.updateMetadataV2 = this.updateMetadataV2.bind(this)
72
+ this.uploadProfileImagesV2 = this.uploadProfileImagesV2.bind(this)
73
+ this.createEntityManagerUserV2 = this.createEntityManagerUserV2.bind(this)
74
+ this._waitForDiscoveryToIndexUser =
75
+ this._waitForDiscoveryToIndexUser.bind(this)
76
+
71
77
  // For adding replica set to users on sign up
72
78
  this.assignReplicaSet = this.assignReplicaSet.bind(this)
73
79
 
@@ -440,6 +446,35 @@ export class Users extends Base {
440
446
  return metadata
441
447
  }
442
448
 
449
+ async uploadProfileImagesV2(
450
+ profilePictureFile: File,
451
+ coverPhotoFile: File,
452
+ metadata: UserMetadata
453
+ ) {
454
+ let didMetadataUpdate = false
455
+ if (profilePictureFile) {
456
+ const resp = await this.creatorNode.uploadProfilePictureV2(
457
+ profilePictureFile
458
+ )
459
+ metadata.profile_picture_sizes = resp.id
460
+ didMetadataUpdate = true
461
+ }
462
+ if (coverPhotoFile) {
463
+ const resp = await this.creatorNode.uploadCoverPhotoV2(coverPhotoFile)
464
+ metadata.cover_photo_sizes = resp.id
465
+ didMetadataUpdate = true
466
+ }
467
+
468
+ if (didMetadataUpdate) {
469
+ await this.updateMetadataV2({
470
+ newMetadata: metadata,
471
+ userId: metadata.user_id
472
+ })
473
+ }
474
+
475
+ return metadata
476
+ }
477
+
443
478
  async createEntityManagerUser({ metadata }: { metadata: UserMetadata }) {
444
479
  this.REQUIRES(Services.CREATOR_NODE)
445
480
  const phases = {
@@ -555,6 +590,59 @@ export class Users extends Base {
555
590
  }
556
591
  }
557
592
 
593
+ async createEntityManagerUserV2({ metadata }: { metadata: UserMetadata }) {
594
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
595
+
596
+ try {
597
+ // Create the user with EntityMananer
598
+ const userId = await this._generateUserId()
599
+ const manageEntityResponse =
600
+ await this.contracts.EntityManagerClient!.manageEntity(
601
+ userId,
602
+ EntityManagerClient.EntityType.USER,
603
+ userId,
604
+ EntityManagerClient.Action.CREATE,
605
+ 'v2'
606
+ )
607
+ await this._waitForDiscoveryToIndexUser(
608
+ userId,
609
+ manageEntityResponse.txReceipt.blockNumber
610
+ )
611
+
612
+ // Ensure metadata has expected properties
613
+ const newMetadata = this.cleanUserMetadata({ ...metadata })
614
+ this._validateUserMetadata(newMetadata)
615
+
616
+ newMetadata.is_storage_v2 = true
617
+ newMetadata.wallet = this.web3Manager.getWalletAddress()
618
+ newMetadata.user_id = userId
619
+ this.userStateManager.setCurrentUser({
620
+ ...newMetadata,
621
+ // Initialize counts to be 0. We don't want to write this data to backends ever really
622
+ // (hence the cleanUserMetadata above), but we do want to make sure clients
623
+ // can properly "do math" on these numbers.
624
+ followee_count: 0,
625
+ follower_count: 0,
626
+ repost_count: 0
627
+ })
628
+
629
+ // Update metadata on chain to include wallet
630
+ const { blockHash, blockNumber } = await this.updateMetadataV2({
631
+ newMetadata,
632
+ userId
633
+ })
634
+
635
+ return { newMetadata, blockHash, blockNumber }
636
+ } catch (e) {
637
+ const errorMsg = `createEntityManagerUserV2() error: ${e}`
638
+ if (e instanceof Error) {
639
+ e.message = errorMsg
640
+ throw e
641
+ }
642
+ throw new Error(errorMsg)
643
+ }
644
+ }
645
+
558
646
  async updateEntityManagerReplicaSet({
559
647
  userId,
560
648
  primary,
@@ -756,7 +844,6 @@ export class Users extends Base {
756
844
  'UPDATE_CONTENT_NODE_ENDPOINT_ON_CHAIN',
757
845
  UPLOAD_METADATA: 'UPLOAD_METADATA',
758
846
  UPDATE_METADATA_ON_CHAIN: 'UPDATE_METADATA_ON_CHAIN',
759
- UPDATE_USER_ON_CHAIN_OPS: 'UPDATE_USER_ON_CHAIN_OPS',
760
847
  ASSOCIATE_USER: 'ASSOCIATE_USER'
761
848
  }
762
849
  let phase = ''
@@ -865,6 +952,59 @@ export class Users extends Base {
865
952
  }
866
953
  }
867
954
 
955
+ /**
956
+ * Storage V2 version of updateAndUploadMetadata. Only posts to chain and not Content Node.
957
+ */
958
+ async updateMetadataV2({
959
+ newMetadata,
960
+ userId
961
+ }: {
962
+ newMetadata: UserMetadata
963
+ userId: number
964
+ }) {
965
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
966
+ this.IS_OBJECT(newMetadata)
967
+
968
+ const oldMetadata = this.userStateManager.getCurrentUser()
969
+ if (!oldMetadata) {
970
+ throw new Error('No current user.')
971
+ }
972
+
973
+ newMetadata = this.cleanUserMetadata(newMetadata)
974
+ this._validateUserMetadata(newMetadata)
975
+
976
+ try {
977
+ // Write metadata to chain
978
+ const cid = await Utils.fileHasher.generateMetadataCidV1(newMetadata)
979
+ const { txReceipt } =
980
+ await this.contracts.EntityManagerClient!.manageEntity(
981
+ userId,
982
+ EntityManagerClient.EntityType.USER,
983
+ userId,
984
+ EntityManagerClient.Action.UPDATE,
985
+ JSON.stringify({
986
+ cid: cid.toString(),
987
+ data: newMetadata
988
+ })
989
+ )
990
+ const blockNumber = txReceipt.blockNumber
991
+
992
+ // Update libs instance with new user metadata object
993
+ this.userStateManager.setCurrentUser({ ...oldMetadata, ...newMetadata })
994
+ return {
995
+ blockHash: txReceipt.blockHash,
996
+ blockNumber
997
+ }
998
+ } catch (e) {
999
+ const errorMsg = `updateMetadataV2() error: ${e}`
1000
+ if (e instanceof Error) {
1001
+ e.message = errorMsg
1002
+ throw e
1003
+ }
1004
+ throw new Error(errorMsg)
1005
+ }
1006
+ }
1007
+
868
1008
  /**
869
1009
  * If a user's creator_node_endpoint is null, assign a replica set.
870
1010
  * Used during the sanity check and in uploadImage() in files.js
@@ -921,12 +1061,12 @@ export class Users extends Base {
921
1061
  timeoutMs = 60000
922
1062
  ): Promise<void> {
923
1063
  const asyncFn = async () => {
1064
+ const encodedUserId = Utils.encodeHashId(userId)
924
1065
  while (true) {
925
- const encodedUserId = Utils.encodeHashId(userId)
926
1066
  let replicaSet
927
1067
  try {
928
1068
  // If the discovery node has not yet indexed the blocknumber,
929
- // this method will throw an error
1069
+ // this method will throw an error (which we catch and ignore)
930
1070
  // If the user replica set does not exist, it will return an empty object
931
1071
  // which should lead to the method throwing an error
932
1072
  replicaSet = await this.discoveryProvider.getUserReplicaSet({
@@ -959,6 +1099,44 @@ export class Users extends Base {
959
1099
  )
960
1100
  }
961
1101
 
1102
+ async _waitForDiscoveryToIndexUser(
1103
+ userId: number,
1104
+ blockNumber: number,
1105
+ timeoutMs = 60000
1106
+ ): Promise<void> {
1107
+ const asyncFn = async () => {
1108
+ while (true) {
1109
+ // Try to get user. Catch+ignore error if the block number isn't yet indexed
1110
+ let user
1111
+ try {
1112
+ user = (
1113
+ await this.discoveryProvider.getUsers(
1114
+ 1, // limit
1115
+ 0, // offset
1116
+ [userId], // userIds
1117
+ null, // walletAddress
1118
+ null, // handle
1119
+ blockNumber, // minBlockNumber
1120
+ true // includeIncomplete
1121
+ )
1122
+ )?.[0]
1123
+ } catch (err) {}
1124
+
1125
+ // All done (success) if the user was indexed and ID matches
1126
+ if (user?.user_id === userId) {
1127
+ break
1128
+ }
1129
+
1130
+ await Utils.wait(500)
1131
+ }
1132
+ }
1133
+ await Utils.racePromiseWithTimeout(
1134
+ asyncFn(),
1135
+ timeoutMs,
1136
+ `[User:_waitForDiscoveryToIndexUser()] Timeout error after ${timeoutMs}ms`
1137
+ )
1138
+ }
1139
+
962
1140
  _validateUserMetadata(metadata: UserMetadata) {
963
1141
  this.OBJECT_HAS_PROPS(metadata, USER_PROPS, USER_REQUIRED_PROPS)
964
1142
  }
@@ -1,12 +1,71 @@
1
1
  {
2
2
  "contractName": "Wormhole",
3
3
  "abi": [
4
+ {
5
+ "constant": true,
6
+ "inputs": [],
7
+ "name": "DOMAIN_SEPARATOR",
8
+ "outputs": [
9
+ {
10
+ "internalType": "bytes32",
11
+ "name": "",
12
+ "type": "bytes32"
13
+ }
14
+ ],
15
+ "payable": false,
16
+ "stateMutability": "view",
17
+ "type": "function"
18
+ },
19
+ {
20
+ "constant": true,
21
+ "inputs": [],
22
+ "name": "LOCK_ASSETS_TYPEHASH",
23
+ "outputs": [
24
+ {
25
+ "internalType": "bytes32",
26
+ "name": "",
27
+ "type": "bytes32"
28
+ }
29
+ ],
30
+ "payable": false,
31
+ "stateMutability": "view",
32
+ "type": "function"
33
+ },
34
+ {
35
+ "constant": false,
36
+ "inputs": [
37
+ {
38
+ "internalType": "address",
39
+ "name": "_tokenAddress",
40
+ "type": "address"
41
+ },
42
+ {
43
+ "internalType": "address",
44
+ "name": "_wormholeAddress",
45
+ "type": "address"
46
+ }
47
+ ],
48
+ "name": "initialize",
49
+ "outputs": [],
50
+ "payable": false,
51
+ "stateMutability": "nonpayable",
52
+ "type": "function"
53
+ },
54
+ {
55
+ "constant": false,
56
+ "inputs": [],
57
+ "name": "initialize",
58
+ "outputs": [],
59
+ "payable": false,
60
+ "stateMutability": "nonpayable",
61
+ "type": "function"
62
+ },
4
63
  {
5
64
  "constant": false,
6
65
  "inputs": [
7
66
  {
8
67
  "internalType": "address",
9
- "name": "token",
68
+ "name": "from",
10
69
  "type": "address"
11
70
  },
12
71
  {
@@ -14,32 +73,83 @@
14
73
  "name": "amount",
15
74
  "type": "uint256"
16
75
  },
17
- {
18
- "internalType": "uint16",
19
- "name": "recipientChain",
20
- "type": "uint16"
21
- },
22
76
  {
23
77
  "internalType": "bytes32",
24
78
  "name": "recipient",
25
79
  "type": "bytes32"
26
80
  },
81
+ {
82
+ "internalType": "uint8",
83
+ "name": "targetChain",
84
+ "type": "uint8"
85
+ },
86
+ {
87
+ "internalType": "bool",
88
+ "name": "refundDust",
89
+ "type": "bool"
90
+ },
27
91
  {
28
92
  "internalType": "uint256",
29
- "name": "arbiterFee",
93
+ "name": "deadline",
30
94
  "type": "uint256"
31
95
  },
32
96
  {
33
- "internalType": "uint32",
34
- "name": "nonce",
35
- "type": "uint32"
97
+ "internalType": "uint8",
98
+ "name": "v",
99
+ "type": "uint8"
100
+ },
101
+ {
102
+ "internalType": "bytes32",
103
+ "name": "r",
104
+ "type": "bytes32"
105
+ },
106
+ {
107
+ "internalType": "bytes32",
108
+ "name": "s",
109
+ "type": "bytes32"
36
110
  }
37
111
  ],
38
- "name": "transferTokens",
112
+ "name": "lockAssets",
39
113
  "outputs": [],
40
114
  "payable": false,
41
115
  "stateMutability": "nonpayable",
42
116
  "type": "function"
117
+ },
118
+ {
119
+ "constant": true,
120
+ "inputs": [
121
+ {
122
+ "internalType": "address",
123
+ "name": "",
124
+ "type": "address"
125
+ }
126
+ ],
127
+ "name": "nonces",
128
+ "outputs": [
129
+ {
130
+ "internalType": "uint32",
131
+ "name": "",
132
+ "type": "uint32"
133
+ }
134
+ ],
135
+ "payable": false,
136
+ "stateMutability": "view",
137
+ "type": "function"
138
+ },
139
+ {
140
+ "constant": true,
141
+ "inputs": [],
142
+ "name": "token",
143
+ "outputs": [
144
+ {
145
+ "internalType": "address",
146
+ "name": "",
147
+ "type": "address"
148
+ }
149
+ ],
150
+ "payable": false,
151
+ "stateMutability": "view",
152
+ "type": "function"
43
153
  }
44
154
  ]
45
155
  }
@@ -418,6 +418,7 @@ export class CreatorNode {
418
418
  ])
419
419
 
420
420
  // Update metadata to include uploaded CIDs
421
+ updatedMetadata.track_segments = []
421
422
  updatedMetadata.track_cid = audioResp.results['320']
422
423
  if (updatedMetadata.download?.is_downloadable) {
423
424
  updatedMetadata.download.cid = updatedMetadata.track_cid
@@ -435,6 +436,14 @@ export class CreatorNode {
435
436
  return await this.uploadFileV2(file, onProgress, 'img_square')
436
437
  }
437
438
 
439
+ async uploadProfilePictureV2(file: File, onProgress: ProgressCB = () => {}) {
440
+ return await this.uploadFileV2(file, onProgress, 'img_square')
441
+ }
442
+
443
+ async uploadCoverPhotoV2(file: File, onProgress: ProgressCB = () => {}) {
444
+ return await this.uploadFileV2(file, onProgress, 'img_backdrop')
445
+ }
446
+
438
447
  async uploadFileV2(
439
448
  file: File,
440
449
  onProgress: ProgressCB,
@@ -248,7 +248,8 @@ export class DiscoveryProvider {
248
248
  idsArray: Nullable<number[]>,
249
249
  walletAddress?: Nullable<string>,
250
250
  handle?: Nullable<string>,
251
- minBlockNumber?: Nullable<number>
251
+ minBlockNumber?: Nullable<number>,
252
+ includeIncomplete?: Nullable<boolean>
252
253
  ) {
253
254
  const req = Requests.getUsers(
254
255
  limit,
@@ -256,7 +257,8 @@ export class DiscoveryProvider {
256
257
  idsArray,
257
258
  walletAddress,
258
259
  handle,
259
- minBlockNumber
260
+ minBlockNumber,
261
+ includeIncomplete
260
262
  )
261
263
  return await this._makeRequest<Nullable<User[]>>(req)
262
264
  }
@@ -9,7 +9,8 @@ export const getUsers = (
9
9
  idsArray: Nullable<number[]>,
10
10
  walletAddress?: Nullable<string>,
11
11
  handle?: Nullable<string>,
12
- minBlockNumber?: Nullable<number>
12
+ minBlockNumber?: Nullable<number>,
13
+ includeIncomplete?: Nullable<boolean>
13
14
  ) => {
14
15
  type QueryParams = {
15
16
  limit: number
@@ -18,6 +19,7 @@ export const getUsers = (
18
19
  wallet?: string
19
20
  min_block_number?: number
20
21
  id?: string[]
22
+ include_incomplete?: boolean
21
23
  }
22
24
 
23
25
  const queryParams: QueryParams = { limit: limit, offset: offset }
@@ -36,6 +38,9 @@ export const getUsers = (
36
38
  }
37
39
  queryParams.id = idsArray as unknown as string[]
38
40
  }
41
+ if (includeIncomplete != null) {
42
+ queryParams.include_incomplete = includeIncomplete
43
+ }
39
44
 
40
45
  const req = { endpoint: 'users', queryParams }
41
46
 
@@ -4,6 +4,27 @@ import { Utils } from '../../utils/utils'
4
4
 
5
5
  const { decodeHashId } = Utils
6
6
 
7
+ const errors = {
8
+ ...SubmitAndEvaluateError,
9
+ USERBANK_CREATION: 'USERBANK_CREATION'
10
+ }
11
+ const AAO_ERRORS = new Set<string>([
12
+ errors.AAO_ATTESTATION_REJECTION,
13
+ errors.AAO_ATTESTATION_UNKNOWN_RESPONSE
14
+ ])
15
+ // Account for errors from DN aggregation + Solana program
16
+ // CHALLENGE_INCOMPLETE and MISSING_CHALLENGES are already handled in the `submitAndEvaluate` flow -
17
+ // safe to assume those won't work if we see them at this point.
18
+ const NEEDS_RESELECT_ERRORS = new Set<string>([
19
+ errors.INSUFFICIENT_DISCOVERY_NODE_COUNT,
20
+ errors.CHALLENGE_INCOMPLETE,
21
+ errors.MISSING_CHALLENGES
22
+ ])
23
+ const ALREADY_COMPLETE_ERRORS = new Set<string>([
24
+ errors.ALREADY_DISBURSED,
25
+ errors.ALREADY_SENT
26
+ ])
27
+
7
28
  // `BaseRewardsReporter` is intended to be subclassed, and provides
8
29
  // "reporting" functionality to RewardsAttester (i.e. posts to Slack if something notable happens)
9
30
  class BaseRewardsReporter {
@@ -698,6 +719,31 @@ export class RewardsAttester {
698
719
  )}], challengeId: [${challengeId}], quorum size: [${this.quorumSize}]}`
699
720
  )
700
721
 
722
+ const feePayerOverride = this._getFeePayer()
723
+ const { error: userbankCreationError } =
724
+ await this.libs.solanaWeb3Manager.createUserBankIfNeeded(
725
+ feePayerOverride,
726
+ wallet
727
+ )
728
+
729
+ if (userbankCreationError) {
730
+ this.logger.error(
731
+ `Failed to create user bank for user [${decodeHashId(userId)}]`,
732
+ userbankCreationError
733
+ )
734
+
735
+ return {
736
+ challengeId,
737
+ userId,
738
+ specifier,
739
+ amount,
740
+ handle,
741
+ wallet,
742
+ completedBlocknumber,
743
+ error: errors.USERBANK_CREATION
744
+ }
745
+ }
746
+
701
747
  const { success, error, aaoErrorCode, phase, nodesToReselect } =
702
748
  await this.libs.Rewards.submitAndEvaluate({
703
749
  challengeId,
@@ -711,7 +757,7 @@ export class RewardsAttester {
711
757
  AAOEndpoint: this.aaoEndpoint,
712
758
  endpoints: this.endpoints,
713
759
  logger: this.logger,
714
- feePayerOverride: this._getFeePayer(),
760
+ feePayerOverride,
715
761
  maxAggregationAttempts: this.maxAggregationAttempts
716
762
  })
717
763
 
@@ -890,26 +936,6 @@ export class RewardsAttester {
890
936
  shouldReselect: boolean
891
937
  failingNodes: string[]
892
938
  }> {
893
- const errors = SubmitAndEvaluateError
894
- const AAO_ERRORS = new Set([
895
- errors.HCAPTCHA,
896
- errors.COGNITO_FLOW,
897
- errors.AAO_ATTESTATION_REJECTION,
898
- errors.AAO_ATTESTATION_UNKNOWN_RESPONSE
899
- ])
900
- // Account for errors from DN aggregation + Solana program
901
- // CHALLENGE_INCOMPLETE and MISSING_CHALLENGES are already handled in the `submitAndEvaluate` flow -
902
- // safe to assume those won't work if we see them at this point.
903
- const NEEDS_RESELECT_ERRORS = new Set([
904
- errors.INSUFFICIENT_DISCOVERY_NODE_COUNT,
905
- errors.CHALLENGE_INCOMPLETE,
906
- errors.MISSING_CHALLENGES
907
- ])
908
- const ALREADY_COMPLETE_ERRORS = new Set([
909
- errors.ALREADY_DISBURSED,
910
- errors.ALREADY_SENT
911
- ])
912
-
913
939
  const noRetry: AttestationResult[] = []
914
940
  const successful: AttestationResult[] = []
915
941
  // Filter our successful responses
@@ -14,7 +14,9 @@ import type {
14
14
  Query,
15
15
  KeyQuery
16
16
  } from 'interface-store'
17
- import type { CID } from 'multiformats/cid'
17
+ import { CID } from 'multiformats/cid'
18
+ import * as json from 'multiformats/codecs/json'
19
+ import { sha256 } from 'multiformats/hashes/sha2'
18
20
 
19
21
  // Base functionality for only hash logic taken from https://github.com/alanshaw/ipfs-only-hash/blob/master/index.js
20
22
 
@@ -154,7 +156,7 @@ export const fileHasher = {
154
156
  * Custom fn to generate the content-hashing logic
155
157
  * @param content a buffer of the content
156
158
  * @param options options for importer
157
- * @returns the CID from content addressing logic
159
+ * @returns the V0 CID from content addressing logic
158
160
  */
159
161
  async hashNonImages(
160
162
  content: Uint8Array,
@@ -227,7 +229,7 @@ export const fileHasher = {
227
229
  },
228
230
 
229
231
  /**
230
- * Generates CID for a non-image file (track segment, track transcode, metadata)
232
+ * Generates CID V0 (46-char string starting with "Qm") for a non-image file (track segment, track transcode, metadata)
231
233
  * @param {Buffer|ReadStream|string} content a single Buffer, a ReadStream, or path to an existing file
232
234
  * @param {Object?} logger
233
235
  * @returns {string} only hash response cid
@@ -240,6 +242,19 @@ export const fileHasher = {
240
242
  return await fileHasher.hashNonImages(buffer)
241
243
  },
242
244
 
245
+ /**
246
+ * Generates CID V1 for a JSON metadata object (NOT the string of the metadata - must be an object).
247
+ * CID<T, 512, SHA_256, 1> represents CID with json codec (512) and sha256 hash using CID V1.
248
+ * Call toString() on the result to get the CID V1 string.
249
+ */
250
+ async generateMetadataCidV1(
251
+ metadata: {}
252
+ ): Promise<CID> {
253
+ const bytes = json.encode(metadata)
254
+ const hash = await sha256.digest(bytes)
255
+ return CID.create(1, json.code, hash)
256
+ },
257
+
243
258
  /**
244
259
  * Wrapper that generates multihashes for image files
245
260
  * @param {Object[]} content an Object[] with the structure [{ path: string, content: buffer }, ...]
@@ -44,6 +44,7 @@ export type UserMetadata = {
44
44
  handle_lc: string
45
45
  is_deactivated: boolean
46
46
  is_verified: boolean
47
+ is_storage_v2: boolean
47
48
  location: Nullable<string>
48
49
  // this should be removed
49
50
  is_creator: boolean