@misofm/sdk 0.15.11 → 0.17.0
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/README.md +210 -73
- package/dist/client.d.ts +224 -68
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +465 -79
- package/dist/client.js.map +1 -1
- package/dist/contracts/composition_credits/composition_credits.d.ts +10 -10
- package/dist/contracts/composition_credits/composition_credits.d.ts.map +1 -1
- package/dist/contracts/composition_credits/composition_credits.js.map +1 -1
- package/dist/contracts/composition_credits/composition_party_role.d.ts +1 -1
- package/dist/contracts/composition_credits/composition_party_role.d.ts.map +1 -1
- package/dist/contracts/composition_credits/composition_party_role.js.map +1 -1
- package/dist/contracts/composition_credits/deps/miso_credit/credit.d.ts.map +1 -1
- package/dist/contracts/composition_credits/deps/miso_credit/credit.js.map +1 -1
- package/dist/contracts/composition_credits/deps/sui/vec_map.d.ts.map +1 -1
- package/dist/contracts/composition_credits/deps/sui/vec_map.js.map +1 -1
- package/dist/contracts/composition_routed_stake/composition_routed_stake.d.ts +29 -93
- package/dist/contracts/composition_routed_stake/composition_routed_stake.d.ts.map +1 -1
- package/dist/contracts/composition_routed_stake/composition_routed_stake.js +19 -79
- package/dist/contracts/composition_routed_stake/composition_routed_stake.js.map +1 -1
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts +22 -106
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts.map +1 -1
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js +16 -98
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js.map +1 -1
- package/dist/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.d.ts +90 -0
- package/dist/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.d.ts.map +1 -0
- package/dist/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.js +85 -0
- package/dist/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.js.map +1 -0
- package/dist/contracts/{party_wallet → composition_royalty_pool_plugin}/witness.d.ts +1 -1
- package/dist/contracts/composition_royalty_pool_plugin/witness.d.ts.map +1 -0
- package/dist/contracts/{recording_royalty_pool → composition_royalty_pool_plugin}/witness.js +1 -1
- package/dist/contracts/composition_royalty_pool_plugin/witness.js.map +1 -0
- package/dist/contracts/cover_art/cover_art.d.ts +4 -4
- package/dist/contracts/cover_art/cover_art.d.ts.map +1 -1
- package/dist/contracts/cover_art/cover_art.js.map +1 -1
- package/dist/contracts/cover_art/deps/ori/walrus_data.d.ts +4 -4
- package/dist/contracts/cover_art/deps/ori/walrus_data.d.ts.map +1 -1
- package/dist/contracts/cover_art/deps/ori/walrus_data.js.map +1 -1
- package/dist/contracts/genre/genre.d.ts +6 -6
- package/dist/contracts/genre/genre.d.ts.map +1 -1
- package/dist/contracts/genre/genre.js.map +1 -1
- package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.d.ts +1 -1
- package/dist/contracts/miso_record/deps/std/type_name.d.ts.map +1 -0
- package/dist/contracts/miso_record/deps/std/type_name.js.map +1 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.d.ts +21 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.d.ts.map +1 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.js +20 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.js.map +1 -0
- package/dist/contracts/miso_record/pressing.d.ts +327 -0
- package/dist/contracts/miso_record/pressing.d.ts.map +1 -0
- package/dist/contracts/miso_record/pressing.js +345 -0
- package/dist/contracts/miso_record/pressing.js.map +1 -0
- package/dist/contracts/miso_record/record.d.ts +190 -0
- package/dist/contracts/miso_record/record.d.ts.map +1 -0
- package/dist/contracts/miso_record/record.js +226 -0
- package/dist/contracts/miso_record/record.js.map +1 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts +18 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts.map +1 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.js +20 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.js.map +1 -0
- package/dist/contracts/miso_record_shop/listing.d.ts +387 -0
- package/dist/contracts/miso_record_shop/listing.d.ts.map +1 -0
- package/dist/contracts/miso_record_shop/listing.js +380 -0
- package/dist/contracts/miso_record_shop/listing.js.map +1 -0
- package/dist/contracts/{release_revenue_distributor → miso_record_shop}/witness.d.ts +2 -2
- package/dist/contracts/miso_record_shop/witness.d.ts.map +1 -0
- package/dist/contracts/{release_revenue_distributor → miso_record_shop}/witness.js +2 -2
- package/dist/contracts/miso_record_shop/witness.js.map +1 -0
- package/dist/contracts/party_wallet/party_wallet.d.ts +41 -168
- package/dist/contracts/party_wallet/party_wallet.d.ts.map +1 -1
- package/dist/contracts/party_wallet/party_wallet.js +27 -152
- package/dist/contracts/party_wallet/party_wallet.js.map +1 -1
- package/dist/contracts/recording_advisory/recording_advisory.d.ts +3 -3
- package/dist/contracts/recording_advisory/recording_advisory.d.ts.map +1 -1
- package/dist/contracts/recording_advisory/recording_advisory.js.map +1 -1
- package/dist/contracts/recording_credits/deps/miso_credit/credit.d.ts.map +1 -1
- package/dist/contracts/recording_credits/deps/miso_credit/credit.js.map +1 -1
- package/dist/contracts/recording_credits/deps/sui/vec_map.d.ts.map +1 -1
- package/dist/contracts/recording_credits/deps/sui/vec_map.js.map +1 -1
- package/dist/contracts/recording_credits/deps/sui/vec_set.d.ts.map +1 -1
- package/dist/contracts/recording_credits/deps/sui/vec_set.js.map +1 -1
- package/dist/contracts/recording_credits/recording_credits.d.ts +20 -20
- package/dist/contracts/recording_credits/recording_credits.d.ts.map +1 -1
- package/dist/contracts/recording_credits/recording_credits.js.map +1 -1
- package/dist/contracts/recording_credits/recording_party_role.d.ts +30 -30
- package/dist/contracts/recording_credits/recording_party_role.d.ts.map +1 -1
- package/dist/contracts/recording_credits/recording_party_role.js.map +1 -1
- package/dist/contracts/recording_language/deps/language_code/language_code.d.ts +1 -1
- package/dist/contracts/recording_language/deps/language_code/language_code.d.ts.map +1 -1
- package/dist/contracts/recording_language/deps/language_code/language_code.js.map +1 -1
- package/dist/contracts/recording_language/recording_language.d.ts +4 -4
- package/dist/contracts/recording_language/recording_language.d.ts.map +1 -1
- package/dist/contracts/recording_language/recording_language.js.map +1 -1
- package/dist/contracts/recording_master_reference/deps/ori/walrus_data.d.ts +4 -4
- package/dist/contracts/recording_master_reference/deps/ori/walrus_data.d.ts.map +1 -1
- package/dist/contracts/recording_master_reference/deps/ori/walrus_data.js.map +1 -1
- package/dist/contracts/recording_master_reference/recording_master_reference.d.ts +6 -6
- package/dist/contracts/recording_master_reference/recording_master_reference.d.ts.map +1 -1
- package/dist/contracts/recording_master_reference/recording_master_reference.js.map +1 -1
- package/dist/contracts/recording_preview/deps/ori/walrus_data.d.ts +4 -4
- package/dist/contracts/recording_preview/deps/ori/walrus_data.d.ts.map +1 -1
- package/dist/contracts/recording_preview/deps/ori/walrus_data.js.map +1 -1
- package/dist/contracts/recording_preview/recording_preview.d.ts +6 -6
- package/dist/contracts/recording_preview/recording_preview.d.ts.map +1 -1
- package/dist/contracts/recording_preview/recording_preview.js.map +1 -1
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts +21 -109
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts.map +1 -1
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js +15 -100
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js.map +1 -1
- package/dist/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.d.ts +92 -0
- package/dist/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.d.ts.map +1 -0
- package/dist/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.js +85 -0
- package/dist/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.js.map +1 -0
- package/dist/contracts/{composition_royalty_pool → recording_royalty_pool_plugin}/witness.d.ts +1 -1
- package/dist/contracts/recording_royalty_pool_plugin/witness.d.ts.map +1 -0
- package/dist/contracts/{party_wallet → recording_royalty_pool_plugin}/witness.js +1 -1
- package/dist/contracts/recording_royalty_pool_plugin/witness.js.map +1 -0
- package/dist/contracts/release_cover_art/deps/{0x340057f2174fb59e4626742dd2b46c662237837b6187450cb59e4976ce7eac78 → 0xf35cf353a62cef01084b51a9cf3da4c64c8724685ad1862f2f8284b71bd26c1a}/walrus_data.d.ts +7 -7
- package/dist/contracts/release_cover_art/deps/{0x340057f2174fb59e4626742dd2b46c662237837b6187450cb59e4976ce7eac78 → 0xf35cf353a62cef01084b51a9cf3da4c64c8724685ad1862f2f8284b71bd26c1a}/walrus_data.d.ts.map +1 -1
- package/dist/contracts/release_cover_art/deps/{0x340057f2174fb59e4626742dd2b46c662237837b6187450cb59e4976ce7eac78 → 0xf35cf353a62cef01084b51a9cf3da4c64c8724685ad1862f2f8284b71bd26c1a}/walrus_data.js +1 -1
- package/dist/contracts/release_cover_art/deps/{0x340057f2174fb59e4626742dd2b46c662237837b6187450cb59e4976ce7eac78 → 0xf35cf353a62cef01084b51a9cf3da4c64c8724685ad1862f2f8284b71bd26c1a}/walrus_data.js.map +1 -1
- package/dist/contracts/release_cover_art/deps/cover_art/cover_art.d.ts +7 -7
- package/dist/contracts/release_cover_art/deps/cover_art/cover_art.d.ts.map +1 -1
- package/dist/contracts/release_cover_art/deps/cover_art/cover_art.js +1 -1
- package/dist/contracts/release_cover_art/deps/cover_art/cover_art.js.map +1 -1
- package/dist/contracts/release_cover_art/deps/per_track/per_track.d.ts.map +1 -1
- package/dist/contracts/release_cover_art/deps/per_track/per_track.js.map +1 -1
- package/dist/contracts/release_cover_art/release_cover_art.d.ts +23 -23
- package/dist/contracts/release_cover_art/release_cover_art.d.ts.map +1 -1
- package/dist/contracts/release_cover_art/release_cover_art.js.map +1 -1
- package/dist/contracts/release_credits/deps/miso_credit/credit.d.ts.map +1 -1
- package/dist/contracts/release_credits/deps/miso_credit/credit.js.map +1 -1
- package/dist/contracts/release_credits/deps/sui/vec_map.d.ts.map +1 -1
- package/dist/contracts/release_credits/deps/sui/vec_map.js.map +1 -1
- package/dist/contracts/release_credits/release_credits.d.ts +10 -10
- package/dist/contracts/release_credits/release_credits.d.ts.map +1 -1
- package/dist/contracts/release_credits/release_credits.js.map +1 -1
- package/dist/contracts/release_description/release_description.d.ts +4 -4
- package/dist/contracts/release_description/release_description.d.ts.map +1 -1
- package/dist/contracts/release_description/release_description.js.map +1 -1
- package/dist/contracts/release_dsp_link/release_dsp_link.d.ts +41 -42
- package/dist/contracts/release_dsp_link/release_dsp_link.d.ts.map +1 -1
- package/dist/contracts/release_dsp_link/release_dsp_link.js +4 -5
- package/dist/contracts/release_dsp_link/release_dsp_link.js.map +1 -1
- package/dist/contracts/release_genre/deps/per_track/per_track.d.ts.map +1 -1
- package/dist/contracts/release_genre/deps/per_track/per_track.js.map +1 -1
- package/dist/contracts/release_genre/release_genre.d.ts +15 -15
- package/dist/contracts/release_genre/release_genre.d.ts.map +1 -1
- package/dist/contracts/release_genre/release_genre.js.map +1 -1
- package/dist/contracts/release_kind/release_kind.d.ts +4 -4
- package/dist/contracts/release_kind/release_kind.d.ts.map +1 -1
- package/dist/contracts/release_kind/release_kind.js.map +1 -1
- package/dist/contracts/release_revenue_distributor/release_revenue_distributor.d.ts +46 -59
- package/dist/contracts/release_revenue_distributor/release_revenue_distributor.d.ts.map +1 -1
- package/dist/contracts/release_revenue_distributor/release_revenue_distributor.js +27 -48
- package/dist/contracts/release_revenue_distributor/release_revenue_distributor.js.map +1 -1
- package/dist/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.d.ts +75 -0
- package/dist/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.d.ts.map +1 -0
- package/dist/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.js +80 -0
- package/dist/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.js.map +1 -0
- package/dist/contracts/{recording_royalty_pool → release_revenue_distributor_plugin}/witness.d.ts +1 -1
- package/dist/contracts/release_revenue_distributor_plugin/witness.d.ts.map +1 -0
- package/dist/contracts/{composition_royalty_pool → release_revenue_distributor_plugin}/witness.js +1 -1
- package/dist/contracts/release_revenue_distributor_plugin/witness.js.map +1 -0
- package/dist/contracts/routed_stake/deps/royalty_pool/stake.d.ts +5 -5
- package/dist/contracts/routed_stake/deps/royalty_pool/stake.d.ts.map +1 -1
- package/dist/contracts/routed_stake/deps/royalty_pool/stake.js.map +1 -1
- package/dist/contracts/routed_stake/deps/std/type_name.d.ts +1 -1
- package/dist/contracts/routed_stake/deps/std/type_name.d.ts.map +1 -1
- package/dist/contracts/routed_stake/deps/std/type_name.js.map +1 -1
- package/dist/contracts/routed_stake/deps/sui/balance.d.ts +1 -1
- package/dist/contracts/routed_stake/deps/sui/balance.d.ts.map +1 -1
- package/dist/contracts/routed_stake/deps/sui/balance.js.map +1 -1
- package/dist/contracts/routed_stake/deps/sui/vec_map.d.ts.map +1 -1
- package/dist/contracts/routed_stake/deps/sui/vec_map.js.map +1 -1
- package/dist/contracts/routed_stake/routed_stake.d.ts +15 -15
- package/dist/contracts/routed_stake/routed_stake.d.ts.map +1 -1
- package/dist/contracts/routed_stake/routed_stake.js.map +1 -1
- package/dist/contracts/royalty_pool/deps/std/type_name.d.ts +1 -1
- package/dist/contracts/royalty_pool/deps/std/type_name.d.ts.map +1 -1
- package/dist/contracts/royalty_pool/deps/std/type_name.js.map +1 -1
- package/dist/contracts/royalty_pool/deps/sui/balance.d.ts +1 -1
- package/dist/contracts/royalty_pool/deps/sui/balance.d.ts.map +1 -1
- package/dist/contracts/royalty_pool/deps/sui/balance.js.map +1 -1
- package/dist/contracts/royalty_pool/deps/sui/vec_map.d.ts.map +1 -1
- package/dist/contracts/royalty_pool/deps/sui/vec_map.js.map +1 -1
- package/dist/contracts/royalty_pool/pool.d.ts +19 -19
- package/dist/contracts/royalty_pool/pool.d.ts.map +1 -1
- package/dist/contracts/royalty_pool/pool.js.map +1 -1
- package/dist/contracts/royalty_pool/stake.d.ts +9 -9
- package/dist/contracts/royalty_pool/stake.d.ts.map +1 -1
- package/dist/contracts/royalty_pool/stake.js.map +1 -1
- package/dist/contracts/vault/deps/sui/bag.d.ts +2 -2
- package/dist/contracts/vault/deps/sui/bag.d.ts.map +1 -1
- package/dist/contracts/vault/deps/sui/bag.js.map +1 -1
- package/dist/contracts/vault/deps/sui/borrow.d.ts.map +1 -1
- package/dist/contracts/vault/deps/sui/borrow.js.map +1 -1
- package/dist/contracts/vault/vault.d.ts +19 -93
- package/dist/contracts/vault/vault.d.ts.map +1 -1
- package/dist/contracts/vault/vault.js +16 -95
- package/dist/contracts/vault/vault.js.map +1 -1
- package/dist/contracts.d.ts +6 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +9 -5
- package/dist/contracts.js.map +1 -1
- package/dist/deployments.d.ts +156 -77
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +146 -32
- package/dist/deployments.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +23 -0
- package/dist/internal.js.map +1 -1
- package/dist/mix.d.ts +144 -0
- package/dist/mix.d.ts.map +1 -0
- package/dist/mix.js +656 -0
- package/dist/mix.js.map +1 -0
- package/dist/pressing.d.ts +72 -156
- package/dist/pressing.d.ts.map +1 -1
- package/dist/pressing.js +269 -261
- package/dist/pressing.js.map +1 -1
- package/dist/publication.d.ts +6 -6
- package/dist/publication.d.ts.map +1 -1
- package/dist/publication.js +134 -90
- package/dist/publication.js.map +1 -1
- package/dist/read/catalog.d.ts +8 -5
- package/dist/read/catalog.d.ts.map +1 -1
- package/dist/read/catalog.js +47 -35
- package/dist/read/catalog.js.map +1 -1
- package/dist/read/config.d.ts +6 -4
- package/dist/read/config.d.ts.map +1 -1
- package/dist/read/config.js +10 -3
- package/dist/read/config.js.map +1 -1
- package/dist/read/index.d.ts +1 -1
- package/dist/read/index.d.ts.map +1 -1
- package/dist/read/index.js +1 -1
- package/dist/read/index.js.map +1 -1
- package/dist/read/internal/scalars.d.ts.map +1 -1
- package/dist/read/internal/scalars.js +4 -3
- package/dist/read/internal/scalars.js.map +1 -1
- package/dist/read/internal/walrus.d.ts.map +1 -1
- package/dist/read/internal/walrus.js +2 -1
- package/dist/read/internal/walrus.js.map +1 -1
- package/dist/read/receipts.d.ts +21 -12
- package/dist/read/receipts.d.ts.map +1 -1
- package/dist/read/receipts.js +159 -56
- package/dist/read/receipts.js.map +1 -1
- package/dist/read/types.d.ts +34 -24
- package/dist/read/types.d.ts.map +1 -1
- package/dist/read/wallet.d.ts.map +1 -1
- package/dist/read/wallet.js +31 -46
- package/dist/read/wallet.js.map +1 -1
- package/dist/vault.d.ts +81 -48
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +173 -174
- package/dist/vault.js.map +1 -1
- package/package.json +17 -7
- package/src/client.ts +618 -120
- package/src/contracts/composition_credits/composition_credits.ts +3 -2
- package/src/contracts/composition_credits/composition_party_role.ts +5 -4
- package/src/contracts/composition_credits/deps/miso_credit/credit.ts +1 -0
- package/src/contracts/composition_credits/deps/sui/vec_map.ts +1 -0
- package/src/contracts/composition_routed_stake/composition_routed_stake.ts +36 -146
- package/src/contracts/composition_royalty_pool/composition_royalty_pool.ts +28 -169
- package/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts +165 -0
- package/src/contracts/{recording_royalty_pool → composition_royalty_pool_plugin}/witness.ts +2 -1
- package/src/contracts/cover_art/cover_art.ts +2 -1
- package/src/contracts/cover_art/deps/ori/walrus_data.ts +1 -0
- package/src/contracts/genre/genre.ts +3 -2
- package/src/contracts/{miso_pressing → miso_record}/deps/std/type_name.ts +1 -0
- package/src/contracts/miso_record/deps/sui/vec_set.ts +22 -0
- package/src/contracts/miso_record/pressing.ts +523 -0
- package/src/contracts/miso_record/record.ts +339 -0
- package/src/contracts/miso_record_shop/deps/std/type_name.ts +23 -0
- package/src/contracts/miso_record_shop/listing.ts +603 -0
- package/src/contracts/{release_revenue_distributor → miso_record_shop}/witness.ts +3 -2
- package/src/contracts/party_wallet/party_wallet.ts +41 -256
- package/src/contracts/recording_advisory/recording_advisory.ts +4 -3
- package/src/contracts/recording_credits/deps/miso_credit/credit.ts +1 -0
- package/src/contracts/recording_credits/deps/sui/vec_map.ts +1 -0
- package/src/contracts/recording_credits/deps/sui/vec_set.ts +1 -0
- package/src/contracts/recording_credits/recording_credits.ts +3 -2
- package/src/contracts/recording_credits/recording_party_role.ts +5 -4
- package/src/contracts/recording_language/deps/language_code/language_code.ts +1 -0
- package/src/contracts/recording_language/recording_language.ts +5 -4
- package/src/contracts/recording_master_reference/deps/ori/walrus_data.ts +1 -0
- package/src/contracts/recording_master_reference/recording_master_reference.ts +6 -5
- package/src/contracts/recording_preview/deps/ori/walrus_data.ts +1 -0
- package/src/contracts/recording_preview/recording_preview.ts +2 -1
- package/src/contracts/recording_royalty_pool/recording_royalty_pool.ts +27 -172
- package/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts +167 -0
- package/src/contracts/{party_wallet → recording_royalty_pool_plugin}/witness.ts +2 -1
- package/src/contracts/release_cover_art/deps/{0x340057f2174fb59e4626742dd2b46c662237837b6187450cb59e4976ce7eac78 → 0xf35cf353a62cef01084b51a9cf3da4c64c8724685ad1862f2f8284b71bd26c1a}/walrus_data.ts +2 -1
- package/src/contracts/release_cover_art/deps/cover_art/cover_art.ts +3 -2
- package/src/contracts/release_cover_art/deps/per_track/per_track.ts +3 -2
- package/src/contracts/release_cover_art/release_cover_art.ts +2 -1
- package/src/contracts/release_credits/deps/miso_credit/credit.ts +1 -0
- package/src/contracts/release_credits/deps/sui/vec_map.ts +1 -0
- package/src/contracts/release_credits/release_credits.ts +4 -3
- package/src/contracts/release_credits/release_party_role.ts +4 -4
- package/src/contracts/release_description/release_description.ts +6 -5
- package/src/contracts/release_dsp_link/release_dsp_link.ts +8 -8
- package/src/contracts/release_genre/deps/per_track/per_track.ts +3 -2
- package/src/contracts/release_genre/release_genre.ts +4 -3
- package/src/contracts/release_kind/release_kind.ts +5 -4
- package/src/contracts/release_revenue_distributor/release_revenue_distributor.ts +50 -85
- package/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts +145 -0
- package/src/contracts/{composition_royalty_pool → release_revenue_distributor_plugin}/witness.ts +2 -1
- package/src/contracts/routed_stake/deps/royalty_pool/stake.ts +1 -0
- package/src/contracts/routed_stake/deps/std/type_name.ts +1 -0
- package/src/contracts/routed_stake/deps/sui/balance.ts +1 -0
- package/src/contracts/routed_stake/deps/sui/vec_map.ts +1 -0
- package/src/contracts/routed_stake/routed_stake.ts +4 -3
- package/src/contracts/royalty_pool/deps/std/type_name.ts +1 -0
- package/src/contracts/royalty_pool/deps/sui/balance.ts +1 -0
- package/src/contracts/royalty_pool/deps/sui/vec_map.ts +1 -0
- package/src/contracts/royalty_pool/pool.ts +1 -0
- package/src/contracts/royalty_pool/stake.ts +1 -0
- package/src/contracts/vault/deps/sui/bag.ts +3 -2
- package/src/contracts/vault/deps/sui/borrow.ts +2 -1
- package/src/contracts/vault/vault.ts +27 -152
- package/src/contracts.ts +9 -5
- package/src/deployments.ts +273 -55
- package/src/index.ts +5 -0
- package/src/internal.ts +27 -0
- package/src/mix.ts +910 -0
- package/src/pressing.ts +428 -423
- package/src/publication.ts +148 -101
- package/src/read/catalog.ts +49 -34
- package/src/read/config.ts +19 -8
- package/src/read/index.ts +9 -1
- package/src/read/internal/scalars.ts +4 -2
- package/src/read/internal/walrus.ts +2 -1
- package/src/read/receipts.ts +181 -66
- package/src/read/types.ts +35 -22
- package/src/read/wallet.ts +29 -50
- package/src/vault.ts +275 -271
- package/dist/contracts/composition_routed_stake/witness.d.ts +0 -7
- package/dist/contracts/composition_routed_stake/witness.d.ts.map +0 -1
- package/dist/contracts/composition_routed_stake/witness.js +0 -9
- package/dist/contracts/composition_routed_stake/witness.js.map +0 -1
- package/dist/contracts/composition_royalty_pool/witness.d.ts.map +0 -1
- package/dist/contracts/composition_royalty_pool/witness.js.map +0 -1
- package/dist/contracts/miso_pressing/certificate.d.ts +0 -89
- package/dist/contracts/miso_pressing/certificate.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/certificate.js +0 -108
- package/dist/contracts/miso_pressing/certificate.js.map +0 -1
- package/dist/contracts/miso_pressing/deps/std/type_name.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/deps/std/type_name.js.map +0 -1
- package/dist/contracts/miso_pressing/listing.d.ts +0 -434
- package/dist/contracts/miso_pressing/listing.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/listing.js +0 -401
- package/dist/contracts/miso_pressing/listing.js.map +0 -1
- package/dist/contracts/miso_pressing/pressing.d.ts +0 -334
- package/dist/contracts/miso_pressing/pressing.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/pressing.js +0 -298
- package/dist/contracts/miso_pressing/pressing.js.map +0 -1
- package/dist/contracts/party_wallet/witness.d.ts.map +0 -1
- package/dist/contracts/party_wallet/witness.js.map +0 -1
- package/dist/contracts/recording_royalty_pool/witness.d.ts.map +0 -1
- package/dist/contracts/recording_royalty_pool/witness.js.map +0 -1
- package/dist/contracts/release_revenue_distributor/witness.d.ts.map +0 -1
- package/dist/contracts/release_revenue_distributor/witness.js.map +0 -1
- package/src/contracts/composition_routed_stake/witness.ts +0 -11
- package/src/contracts/miso_pressing/certificate.ts +0 -164
- package/src/contracts/miso_pressing/listing.ts +0 -592
- package/src/contracts/miso_pressing/pressing.ts +0 -399
- /package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.js +0 -0
package/src/mix.ts
ADDED
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical Record-gated engine-session contracts.
|
|
6
|
+
*
|
|
7
|
+
* The plaintext Walrus session is the complete public manifest. It contains
|
|
8
|
+
* mixer metadata, one Seal-wrapped AES key, and the encrypted stem blob IDs.
|
|
9
|
+
* There is deliberately no second encrypted mix document and no off-chain
|
|
10
|
+
* pointer table.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EncryptedObject } from "@mysten/seal";
|
|
14
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
15
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
16
|
+
import type {
|
|
17
|
+
Transaction,
|
|
18
|
+
TransactionObjectArgument,
|
|
19
|
+
} from "@mysten/sui/transactions";
|
|
20
|
+
import {
|
|
21
|
+
deriveDynamicFieldID,
|
|
22
|
+
normalizeSuiObjectId,
|
|
23
|
+
parseStructTag,
|
|
24
|
+
} from "@mysten/sui/utils";
|
|
25
|
+
import { getReleaseById, isNotFound } from "@misonetwork/sdk";
|
|
26
|
+
import { WalrusData } from "./contracts/recording_master_reference/deps/ori/walrus_data.ts";
|
|
27
|
+
import * as recordContract from "./contracts/miso_record/record.ts";
|
|
28
|
+
import type { TxThunk } from "./transactions.ts";
|
|
29
|
+
import {
|
|
30
|
+
invokeWithAdminCap,
|
|
31
|
+
type AdminCapAuthority,
|
|
32
|
+
type ObjectInput,
|
|
33
|
+
} from "./vault.ts";
|
|
34
|
+
|
|
35
|
+
export const RECORDING_SESSION_ID_BYTES = 98;
|
|
36
|
+
export const RECORDING_SESSION_ID_SCHEMA = 1 as const;
|
|
37
|
+
export const RECORDING_SESSION_ID_KIND = 1 as const;
|
|
38
|
+
|
|
39
|
+
export const ENGINE_SESSION_SCHEMA_V1 = "miso.engine-session/1" as const;
|
|
40
|
+
export const ENGINE_SESSION_NETWORK_V1 = "testnet" as const;
|
|
41
|
+
export const STEM_CIPHER_FRAMING_V1 =
|
|
42
|
+
"aes-256-gcm-hkdf-stream/2" as const;
|
|
43
|
+
export const STEM_CIPHER_CHUNK_BYTES = 1_048_576 as const;
|
|
44
|
+
export const STEM_CIPHER_TAG_BYTES = 16 as const;
|
|
45
|
+
export const STEM_CIPHER_HEADER_BYTES = 25 as const;
|
|
46
|
+
export const MAX_ENGINE_SESSION_BYTES = 256 * 1024;
|
|
47
|
+
export const MAX_ENGINE_SESSION_STEMS = 32;
|
|
48
|
+
export const MAX_STEM_FLAC_BYTES = 128 * 1024 * 1024;
|
|
49
|
+
export const MAX_SESSION_FLAC_BYTES = 256 * 1024 * 1024;
|
|
50
|
+
export const MAX_STEM_CANONICAL_PCM_BYTES = 384 * 1024 * 1024;
|
|
51
|
+
export const ENGINE_SESSION_SAMPLE_RATES = [
|
|
52
|
+
44_100,
|
|
53
|
+
48_000,
|
|
54
|
+
88_200,
|
|
55
|
+
96_000,
|
|
56
|
+
] as const;
|
|
57
|
+
export type EngineSessionSampleRate =
|
|
58
|
+
(typeof ENGINE_SESSION_SAMPLE_RATES)[number];
|
|
59
|
+
|
|
60
|
+
const MAX_U32 = 0xffff_ffff;
|
|
61
|
+
const ADDRESS_RE = /^0x[0-9a-f]{64}$/;
|
|
62
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
63
|
+
const PCM_IDENTITY_RE = /^sha256:[0-9a-f]{64}$/;
|
|
64
|
+
const BLOB_ID_RE = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
65
|
+
const STEM_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
66
|
+
const PARAM_KEY_RE = /^[a-z0-9_]{1,48}$/;
|
|
67
|
+
const STEM_DOMAIN_PREFIX = new TextEncoder().encode("miso.mix.asset/2\0");
|
|
68
|
+
|
|
69
|
+
export interface RecordingSessionIdentity {
|
|
70
|
+
readonly schema: typeof RECORDING_SESSION_ID_SCHEMA;
|
|
71
|
+
readonly kind: typeof RECORDING_SESSION_ID_KIND;
|
|
72
|
+
readonly recordGateId: string;
|
|
73
|
+
readonly recordingId: string;
|
|
74
|
+
readonly nonce: Uint8Array;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Encode `[schema | kind | gate | recording | 32-byte nonce]`. */
|
|
78
|
+
export function encodeRecordingSessionIdentity(
|
|
79
|
+
identity: Omit<RecordingSessionIdentity, "schema" | "kind"> &
|
|
80
|
+
Partial<Pick<RecordingSessionIdentity, "schema" | "kind">>,
|
|
81
|
+
): Uint8Array {
|
|
82
|
+
if ((identity.schema ?? 1) !== 1) {
|
|
83
|
+
throw new Error("Recording session identity schema must be 1");
|
|
84
|
+
}
|
|
85
|
+
if ((identity.kind ?? 1) !== 1) {
|
|
86
|
+
throw new Error("Recording session identity kind must be 1");
|
|
87
|
+
}
|
|
88
|
+
const gate = addressBytes("recordGateId", identity.recordGateId);
|
|
89
|
+
const recording = addressBytes("recordingId", identity.recordingId);
|
|
90
|
+
if (!(identity.nonce instanceof Uint8Array) || identity.nonce.length !== 32) {
|
|
91
|
+
throw new Error("nonce must be exactly 32 bytes");
|
|
92
|
+
}
|
|
93
|
+
return concatBytes(
|
|
94
|
+
new Uint8Array([1, 1]),
|
|
95
|
+
gate,
|
|
96
|
+
recording,
|
|
97
|
+
identity.nonce,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Strictly parse one exact identity; short and trailing bytes are rejected. */
|
|
102
|
+
export function parseRecordingSessionIdentity(
|
|
103
|
+
bytes: Uint8Array,
|
|
104
|
+
): RecordingSessionIdentity {
|
|
105
|
+
if (!(bytes instanceof Uint8Array) || bytes.length !== RECORDING_SESSION_ID_BYTES) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Recording session identity must be exactly ${RECORDING_SESSION_ID_BYTES} bytes`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (bytes[0] !== 1) {
|
|
111
|
+
throw new Error(`unsupported Recording session identity schema ${bytes[0]}`);
|
|
112
|
+
}
|
|
113
|
+
if (bytes[1] !== 1) {
|
|
114
|
+
throw new Error(`unsupported Recording session identity kind ${bytes[1]}`);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
schema: 1,
|
|
118
|
+
kind: 1,
|
|
119
|
+
recordGateId: bytesToAddress(bytes.subarray(2, 34)),
|
|
120
|
+
recordingId: bytesToAddress(bytes.subarray(34, 66)),
|
|
121
|
+
nonce: bytes.slice(66, 98),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function recordingSessionIdentityHex(
|
|
126
|
+
identity: Parameters<typeof encodeRecordingSessionIdentity>[0],
|
|
127
|
+
): string {
|
|
128
|
+
return bytesToHex(encodeRecordingSessionIdentity(identity));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Stable unique HKDF/AAD domain for one encrypted stem. */
|
|
132
|
+
export function stemCipherDomain(stemId: string): Uint8Array {
|
|
133
|
+
if (!STEM_ID_RE.test(stemId)) {
|
|
134
|
+
throw new Error("stemId must be a portable 1..128 byte ASCII identifier");
|
|
135
|
+
}
|
|
136
|
+
const id = new TextEncoder().encode(stemId);
|
|
137
|
+
return concatBytes(STEM_DOMAIN_PREFIX, new Uint8Array([1, id.length]), id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface EngineSessionEffectPreset {
|
|
141
|
+
readonly slot: number;
|
|
142
|
+
readonly bypassed: boolean;
|
|
143
|
+
readonly params: Readonly<Record<string, number>>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface EngineSessionStemPreset {
|
|
147
|
+
readonly faderDb: number;
|
|
148
|
+
readonly pan: number;
|
|
149
|
+
readonly muted: boolean;
|
|
150
|
+
readonly soloed: boolean;
|
|
151
|
+
readonly effects: readonly EngineSessionEffectPreset[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface EngineSessionStemV1 {
|
|
155
|
+
/** Portable encryption-domain id, independent of PCM/container identity. */
|
|
156
|
+
readonly id: string;
|
|
157
|
+
/** Canonical decoded PCM identity consumed by the stem store. */
|
|
158
|
+
readonly identity: `sha256:${string}`;
|
|
159
|
+
readonly name: string;
|
|
160
|
+
readonly role: string;
|
|
161
|
+
readonly channels: 1 | 2;
|
|
162
|
+
readonly bitDepth: 16 | 24;
|
|
163
|
+
readonly frames: number;
|
|
164
|
+
readonly preset: EngineSessionStemPreset;
|
|
165
|
+
/** Standalone Walrus blob containing framed AES-GCM ciphertext. */
|
|
166
|
+
readonly blobId: string;
|
|
167
|
+
readonly ciphertextSha256: string;
|
|
168
|
+
readonly plainBytes: number;
|
|
169
|
+
readonly cipherBytes: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface EngineSessionV1 {
|
|
173
|
+
readonly schema: typeof ENGINE_SESSION_SCHEMA_V1;
|
|
174
|
+
readonly network: typeof ENGINE_SESSION_NETWORK_V1;
|
|
175
|
+
readonly recordingId: string;
|
|
176
|
+
readonly title: string;
|
|
177
|
+
readonly artist: string;
|
|
178
|
+
readonly sampleRateHz: EngineSessionSampleRate;
|
|
179
|
+
readonly seal: {
|
|
180
|
+
/** Canonical base64url Seal EncryptedObject wrapping exactly 32 AES bytes. */
|
|
181
|
+
readonly encryptedKey: string;
|
|
182
|
+
};
|
|
183
|
+
readonly cipher: {
|
|
184
|
+
readonly framing: typeof STEM_CIPHER_FRAMING_V1;
|
|
185
|
+
readonly chunkSize: typeof STEM_CIPHER_CHUNK_BYTES;
|
|
186
|
+
readonly tagSize: typeof STEM_CIPHER_TAG_BYTES;
|
|
187
|
+
};
|
|
188
|
+
readonly stems: readonly EngineSessionStemV1[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Ciphertext length implied by the fixed authenticated-record framing. */
|
|
192
|
+
export function stemCipherBytes(plainBytes: number): number {
|
|
193
|
+
integer("plainBytes", plainBytes, 1, Number.MAX_SAFE_INTEGER);
|
|
194
|
+
const records = Math.ceil(plainBytes / STEM_CIPHER_CHUNK_BYTES);
|
|
195
|
+
if (records > MAX_U32) throw new Error("cipher record count exceeds u32");
|
|
196
|
+
const bytes =
|
|
197
|
+
STEM_CIPHER_HEADER_BYTES +
|
|
198
|
+
plainBytes +
|
|
199
|
+
records * STEM_CIPHER_TAG_BYTES;
|
|
200
|
+
if (!Number.isSafeInteger(bytes)) {
|
|
201
|
+
throw new Error("cipher byte size exceeds safe JSON integer range");
|
|
202
|
+
}
|
|
203
|
+
return bytes;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Validate exact shape, bounds, identity, and the embedded Seal object. */
|
|
207
|
+
export function assertEngineSessionV1(
|
|
208
|
+
value: unknown,
|
|
209
|
+
): asserts value is EngineSessionV1 {
|
|
210
|
+
const session = exact(value, "session", [
|
|
211
|
+
"schema",
|
|
212
|
+
"network",
|
|
213
|
+
"recordingId",
|
|
214
|
+
"title",
|
|
215
|
+
"artist",
|
|
216
|
+
"sampleRateHz",
|
|
217
|
+
"seal",
|
|
218
|
+
"cipher",
|
|
219
|
+
"stems",
|
|
220
|
+
]);
|
|
221
|
+
literal("schema", session.schema, ENGINE_SESSION_SCHEMA_V1);
|
|
222
|
+
literal("network", session.network, ENGINE_SESSION_NETWORK_V1);
|
|
223
|
+
address("recordingId", session.recordingId);
|
|
224
|
+
text("title", session.title);
|
|
225
|
+
text("artist", session.artist);
|
|
226
|
+
if (!ENGINE_SESSION_SAMPLE_RATES.includes(session.sampleRateHz as EngineSessionSampleRate)) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`sampleRateHz must be one of ${ENGINE_SESSION_SAMPLE_RATES.join(", ")}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const seal = exact(session.seal, "seal", ["encryptedKey"]);
|
|
233
|
+
if (typeof seal.encryptedKey !== "string") {
|
|
234
|
+
throw new Error("seal.encryptedKey must be canonical base64url");
|
|
235
|
+
}
|
|
236
|
+
const encryptedKey = base64UrlToBytes(seal.encryptedKey);
|
|
237
|
+
const inspected = inspectEngineSessionKey(encryptedKey);
|
|
238
|
+
const identity = parseRecordingSessionIdentity(inspected.innerId);
|
|
239
|
+
if (identity.recordingId !== session.recordingId) {
|
|
240
|
+
throw new Error("Seal identity recording does not match session recordingId");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const cipher = exact(session.cipher, "cipher", [
|
|
244
|
+
"framing",
|
|
245
|
+
"chunkSize",
|
|
246
|
+
"tagSize",
|
|
247
|
+
]);
|
|
248
|
+
literal("cipher.framing", cipher.framing, STEM_CIPHER_FRAMING_V1);
|
|
249
|
+
literal("cipher.chunkSize", cipher.chunkSize, STEM_CIPHER_CHUNK_BYTES);
|
|
250
|
+
literal("cipher.tagSize", cipher.tagSize, STEM_CIPHER_TAG_BYTES);
|
|
251
|
+
|
|
252
|
+
if (
|
|
253
|
+
!Array.isArray(session.stems) ||
|
|
254
|
+
session.stems.length < 1 ||
|
|
255
|
+
session.stems.length > MAX_ENGINE_SESSION_STEMS
|
|
256
|
+
) {
|
|
257
|
+
throw new Error(`stems must contain 1..${MAX_ENGINE_SESSION_STEMS} entries`);
|
|
258
|
+
}
|
|
259
|
+
const ids = new Set<string>();
|
|
260
|
+
const identities = new Set<string>();
|
|
261
|
+
let aggregate = 0;
|
|
262
|
+
for (const [index, raw] of session.stems.entries()) {
|
|
263
|
+
const stem = exact(raw, `stems[${index}]`, [
|
|
264
|
+
"id",
|
|
265
|
+
"identity",
|
|
266
|
+
"name",
|
|
267
|
+
"role",
|
|
268
|
+
"channels",
|
|
269
|
+
"bitDepth",
|
|
270
|
+
"frames",
|
|
271
|
+
"preset",
|
|
272
|
+
"blobId",
|
|
273
|
+
"ciphertextSha256",
|
|
274
|
+
"plainBytes",
|
|
275
|
+
"cipherBytes",
|
|
276
|
+
]);
|
|
277
|
+
if (typeof stem.id !== "string" || !STEM_ID_RE.test(stem.id)) {
|
|
278
|
+
throw new Error(`stems[${index}].id is not portable`);
|
|
279
|
+
}
|
|
280
|
+
if (ids.has(stem.id)) throw new Error(`duplicate stem id ${stem.id}`);
|
|
281
|
+
ids.add(stem.id);
|
|
282
|
+
if (typeof stem.identity !== "string" || !PCM_IDENTITY_RE.test(stem.identity)) {
|
|
283
|
+
throw new Error(`stems[${index}].identity is not canonical PCM SHA-256`);
|
|
284
|
+
}
|
|
285
|
+
if (identities.has(stem.identity)) {
|
|
286
|
+
throw new Error(`duplicate stem identity ${stem.identity}`);
|
|
287
|
+
}
|
|
288
|
+
identities.add(stem.identity);
|
|
289
|
+
text(`stems[${index}].name`, stem.name);
|
|
290
|
+
text(`stems[${index}].role`, stem.role);
|
|
291
|
+
integer(`stems[${index}].channels`, stem.channels, 1, 2);
|
|
292
|
+
if (stem.bitDepth !== 16 && stem.bitDepth !== 24) {
|
|
293
|
+
throw new Error(`stems[${index}].bitDepth must be 16 or 24`);
|
|
294
|
+
}
|
|
295
|
+
integer(`stems[${index}].frames`, stem.frames, 1, Number.MAX_SAFE_INTEGER);
|
|
296
|
+
const pcmBytes =
|
|
297
|
+
BigInt(stem.frames as number) *
|
|
298
|
+
BigInt(stem.channels as number) *
|
|
299
|
+
BigInt((stem.bitDepth as number) / 8);
|
|
300
|
+
if (pcmBytes > BigInt(MAX_STEM_CANONICAL_PCM_BYTES)) {
|
|
301
|
+
throw new Error(`stems[${index}] canonical PCM exceeds the browser limit`);
|
|
302
|
+
}
|
|
303
|
+
assertPreset(stem.preset, `stems[${index}].preset`);
|
|
304
|
+
blobId(`stems[${index}].blobId`, stem.blobId);
|
|
305
|
+
sha256(`stems[${index}].ciphertextSha256`, stem.ciphertextSha256);
|
|
306
|
+
integer(
|
|
307
|
+
`stems[${index}].plainBytes`,
|
|
308
|
+
stem.plainBytes,
|
|
309
|
+
1,
|
|
310
|
+
MAX_STEM_FLAC_BYTES,
|
|
311
|
+
);
|
|
312
|
+
integer(
|
|
313
|
+
`stems[${index}].cipherBytes`,
|
|
314
|
+
stem.cipherBytes,
|
|
315
|
+
1,
|
|
316
|
+
Number.MAX_SAFE_INTEGER,
|
|
317
|
+
);
|
|
318
|
+
if (stem.cipherBytes !== stemCipherBytes(stem.plainBytes as number)) {
|
|
319
|
+
throw new Error(`stems[${index}].cipherBytes contradicts its framing`);
|
|
320
|
+
}
|
|
321
|
+
aggregate += stem.plainBytes as number;
|
|
322
|
+
if (!Number.isSafeInteger(aggregate) || aggregate > MAX_SESSION_FLAC_BYTES) {
|
|
323
|
+
throw new Error("session FLAC bytes exceed the aggregate browser limit");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Canonical plaintext bytes uploaded as the Recording's session blob. */
|
|
329
|
+
export function encodeEngineSessionV1(session: EngineSessionV1): Uint8Array {
|
|
330
|
+
assertEngineSessionV1(session);
|
|
331
|
+
const bytes = new TextEncoder().encode(canonicalJson(session));
|
|
332
|
+
if (bytes.length > MAX_ENGINE_SESSION_BYTES) {
|
|
333
|
+
throw new Error(`engine session exceeds ${MAX_ENGINE_SESSION_BYTES} bytes`);
|
|
334
|
+
}
|
|
335
|
+
return bytes;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Parse only canonical JSON: unknown keys, whitespace, and trailing data fail. */
|
|
339
|
+
export function parseEngineSessionV1(
|
|
340
|
+
input: Uint8Array | string,
|
|
341
|
+
): EngineSessionV1 {
|
|
342
|
+
const bytes =
|
|
343
|
+
typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
344
|
+
if (!(bytes instanceof Uint8Array) || bytes.length < 1 || bytes.length > MAX_ENGINE_SESSION_BYTES) {
|
|
345
|
+
throw new Error(`engine session must be 1..${MAX_ENGINE_SESSION_BYTES} bytes`);
|
|
346
|
+
}
|
|
347
|
+
let source: string;
|
|
348
|
+
try {
|
|
349
|
+
source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
350
|
+
} catch {
|
|
351
|
+
throw new Error("engine session is not valid UTF-8");
|
|
352
|
+
}
|
|
353
|
+
let value: unknown;
|
|
354
|
+
try {
|
|
355
|
+
value = JSON.parse(source);
|
|
356
|
+
} catch {
|
|
357
|
+
throw new Error("engine session is not valid JSON");
|
|
358
|
+
}
|
|
359
|
+
assertEngineSessionV1(value);
|
|
360
|
+
if (canonicalJson(value) !== source) {
|
|
361
|
+
throw new Error("engine session must use canonical JSON with no trailing bytes");
|
|
362
|
+
}
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export interface InspectedEngineSessionKey {
|
|
367
|
+
readonly packageId: string;
|
|
368
|
+
readonly innerId: Uint8Array;
|
|
369
|
+
readonly services: readonly (readonly [string, number])[];
|
|
370
|
+
readonly threshold: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Parse and constrain the official Seal object carrying the session AES key. */
|
|
374
|
+
export function inspectEngineSessionKey(
|
|
375
|
+
bytes: Uint8Array,
|
|
376
|
+
): InspectedEngineSessionKey {
|
|
377
|
+
let encrypted: typeof EncryptedObject.$inferType;
|
|
378
|
+
try {
|
|
379
|
+
encrypted = EncryptedObject.parse(bytes);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`seal.encryptedKey is not a Seal EncryptedObject: ${error instanceof Error ? error.message : error}`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const canonical = EncryptedObject.serialize(encrypted).toBytes();
|
|
386
|
+
if (
|
|
387
|
+
canonical.length !== bytes.length ||
|
|
388
|
+
canonical.some((byte, index) => byte !== bytes[index])
|
|
389
|
+
) {
|
|
390
|
+
throw new Error("seal.encryptedKey is not canonical");
|
|
391
|
+
}
|
|
392
|
+
if (encrypted.version !== 0) {
|
|
393
|
+
throw new Error(`unsupported Seal version ${encrypted.version}`);
|
|
394
|
+
}
|
|
395
|
+
if (
|
|
396
|
+
encrypted.threshold !== 1 ||
|
|
397
|
+
encrypted.services.length !== 1 ||
|
|
398
|
+
encrypted.services[0]?.[1] !== 1
|
|
399
|
+
) {
|
|
400
|
+
throw new Error("engine session requires one threshold-1 Seal service");
|
|
401
|
+
}
|
|
402
|
+
if (encrypted.encryptedShares.$kind !== "BonehFranklinBLS12381") {
|
|
403
|
+
throw new Error("engine session key uses an unsupported Seal KEM");
|
|
404
|
+
}
|
|
405
|
+
if (
|
|
406
|
+
encrypted.encryptedShares.BonehFranklinBLS12381.encryptedShares.length !== 1
|
|
407
|
+
) {
|
|
408
|
+
throw new Error("Seal encrypted share count is inconsistent");
|
|
409
|
+
}
|
|
410
|
+
if (encrypted.ciphertext.$kind !== "Aes256Gcm") {
|
|
411
|
+
throw new Error("Seal payload must use AES-256-GCM");
|
|
412
|
+
}
|
|
413
|
+
const payload = encrypted.ciphertext.Aes256Gcm;
|
|
414
|
+
if (payload.blob.length !== 48) {
|
|
415
|
+
throw new Error("Seal payload must wrap exactly one 32-byte key");
|
|
416
|
+
}
|
|
417
|
+
if (payload.aad !== null && payload.aad.length !== 0) {
|
|
418
|
+
throw new Error("Seal session-key payload must not carry AAD");
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
packageId: normalizeSuiObjectId(encrypted.packageId),
|
|
422
|
+
innerId: hexToBytes(`0x${encrypted.id}`, RECORDING_SESSION_ID_BYTES, "Seal id"),
|
|
423
|
+
services: encrypted.services.map(([id, weight]) => [
|
|
424
|
+
normalizeSuiObjectId(id),
|
|
425
|
+
weight,
|
|
426
|
+
] as const),
|
|
427
|
+
threshold: encrypted.threshold,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Direct on-chain graph ───────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
const EngineSessionField = bcs.struct("Field", {
|
|
434
|
+
id: bcs.Address,
|
|
435
|
+
name: bcs.tuple([bcs.bool()]),
|
|
436
|
+
value: bcs.struct("EngineSession", { reference: WalrusData }),
|
|
437
|
+
});
|
|
438
|
+
const ENGINE_SESSION_KEY_BYTES = new Uint8Array([0]);
|
|
439
|
+
const ReleaseMixReferenceField = bcs.struct("Field", {
|
|
440
|
+
id: bcs.Address,
|
|
441
|
+
name: bcs.tuple([bcs.bool()]),
|
|
442
|
+
value: bcs.tuple([bcs.vector(bcs.option(WalrusData))]),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
export interface RecordingEngineSessionReference {
|
|
446
|
+
readonly blobId: bigint;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface ReleaseMixReference {
|
|
450
|
+
readonly blobId: bigint;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Read the release-aligned optional descriptor references in track order. */
|
|
454
|
+
export async function getReleaseMixReferences(
|
|
455
|
+
client: ClientWithCoreApi,
|
|
456
|
+
releaseId: string,
|
|
457
|
+
packageId: string,
|
|
458
|
+
): Promise<Array<ReleaseMixReference | null> | null> {
|
|
459
|
+
const fieldId = deriveDynamicFieldID(
|
|
460
|
+
releaseId,
|
|
461
|
+
`${packageId}::release_mix_reference::ExtensionKey`,
|
|
462
|
+
new Uint8Array([0]),
|
|
463
|
+
);
|
|
464
|
+
try {
|
|
465
|
+
const { object } = await client.core.getObject({ objectId: fieldId, include: { content: true } });
|
|
466
|
+
if (!object?.content) throw new Error("Release mix-reference field is missing BCS content");
|
|
467
|
+
const [entries] = ReleaseMixReferenceField.parse(object.content).value;
|
|
468
|
+
return entries.map((entry) => {
|
|
469
|
+
if (entry == null) return null;
|
|
470
|
+
if (entry.$kind !== "Blob" || entry.Blob[1].$kind !== "Unencrypted") {
|
|
471
|
+
throw new Error("Release mix reference is not a plaintext standalone Walrus blob");
|
|
472
|
+
}
|
|
473
|
+
return { blobId: BigInt(entry.Blob[0]) };
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (isNotFound(error)) return null;
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function recordingEngineSessionFieldId(
|
|
482
|
+
recordingId: string,
|
|
483
|
+
packageId: string,
|
|
484
|
+
): string {
|
|
485
|
+
return deriveDynamicFieldID(
|
|
486
|
+
recordingId,
|
|
487
|
+
`${packageId}::recording_engine_session::ExtensionKey`,
|
|
488
|
+
ENGINE_SESSION_KEY_BYTES,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function parseRecordingEngineSessionContent(
|
|
493
|
+
content: Uint8Array,
|
|
494
|
+
): RecordingEngineSessionReference {
|
|
495
|
+
const reference = EngineSessionField.parse(content).value.reference;
|
|
496
|
+
if (reference.$kind !== "Blob") {
|
|
497
|
+
throw new Error("Recording engine session is not a standalone Walrus blob");
|
|
498
|
+
}
|
|
499
|
+
const [id, confidentiality] = reference.Blob;
|
|
500
|
+
if (confidentiality.$kind !== "Unencrypted") {
|
|
501
|
+
throw new Error("Recording engine session is unexpectedly encrypted");
|
|
502
|
+
}
|
|
503
|
+
return { blobId: BigInt(id) };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function getRecordingEngineSession(
|
|
507
|
+
client: ClientWithCoreApi,
|
|
508
|
+
recordingId: string,
|
|
509
|
+
packageId: string,
|
|
510
|
+
): Promise<RecordingEngineSessionReference | null> {
|
|
511
|
+
const fieldId = recordingEngineSessionFieldId(recordingId, packageId);
|
|
512
|
+
const { objects } = await client.core.getObjects({
|
|
513
|
+
objectIds: [fieldId],
|
|
514
|
+
include: { content: true },
|
|
515
|
+
});
|
|
516
|
+
const field = objects[0];
|
|
517
|
+
if (!field) throw new Error("Recording engine-session read returned no result");
|
|
518
|
+
if (field instanceof Error) {
|
|
519
|
+
if (isNotFound(field)) return null;
|
|
520
|
+
throw field;
|
|
521
|
+
}
|
|
522
|
+
if (!field.content) {
|
|
523
|
+
throw new Error("Recording engine-session field is missing BCS content");
|
|
524
|
+
}
|
|
525
|
+
return parseRecordingEngineSessionContent(field.content);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Exact concrete Record read using BCS, never transport-dependent JSON. */
|
|
529
|
+
export async function getRecordReleaseId(
|
|
530
|
+
client: ClientWithCoreApi,
|
|
531
|
+
recordId: string,
|
|
532
|
+
recordPackageId: string,
|
|
533
|
+
): Promise<string> {
|
|
534
|
+
const { object } = await client.core.getObject({
|
|
535
|
+
objectId: recordId,
|
|
536
|
+
include: { content: true },
|
|
537
|
+
});
|
|
538
|
+
const tag = parseStructTag(object.type);
|
|
539
|
+
if (
|
|
540
|
+
tag.address !== normalizeSuiObjectId(recordPackageId) ||
|
|
541
|
+
tag.module !== "record" ||
|
|
542
|
+
tag.name !== "Record" ||
|
|
543
|
+
tag.typeParams.length !== 0
|
|
544
|
+
) {
|
|
545
|
+
throw new Error("recordId is not the configured concrete Miso Record type");
|
|
546
|
+
}
|
|
547
|
+
if (!object.content) throw new Error("Record is missing BCS content");
|
|
548
|
+
const record = recordContract.Record.parse(object.content);
|
|
549
|
+
if (normalizeSuiObjectId(record.id) !== normalizeSuiObjectId(recordId)) {
|
|
550
|
+
throw new Error("Record UID does not match recordId");
|
|
551
|
+
}
|
|
552
|
+
return normalizeSuiObjectId(record.release_id);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export interface ResolvedRecordEngineSession {
|
|
556
|
+
readonly releaseId: string;
|
|
557
|
+
readonly recordingId: string;
|
|
558
|
+
readonly sessionBlobId: string;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Resolve Record → immutable Release track → Recording dynamic field.
|
|
563
|
+
*
|
|
564
|
+
* Every edge is a direct Sui object read. No event scan, dynamic-field list,
|
|
565
|
+
* API hint, or indexer-maintained relation participates in the answer.
|
|
566
|
+
*/
|
|
567
|
+
export async function resolveRecordEngineSession(
|
|
568
|
+
client: ClientWithCoreApi,
|
|
569
|
+
input: {
|
|
570
|
+
readonly recordId: string;
|
|
571
|
+
readonly trackIndex: number;
|
|
572
|
+
readonly recordPackageId: string;
|
|
573
|
+
readonly recordingEngineSessionPackageId: string;
|
|
574
|
+
},
|
|
575
|
+
): Promise<ResolvedRecordEngineSession> {
|
|
576
|
+
integer("trackIndex", input.trackIndex, 0, MAX_U32);
|
|
577
|
+
const releaseId = await getRecordReleaseId(
|
|
578
|
+
client,
|
|
579
|
+
input.recordId,
|
|
580
|
+
input.recordPackageId,
|
|
581
|
+
);
|
|
582
|
+
const release = await getReleaseById(client, releaseId);
|
|
583
|
+
const track = release.tracks[input.trackIndex];
|
|
584
|
+
if (!track) throw new Error("trackIndex is outside the Record's Release");
|
|
585
|
+
const recordingId = normalizeSuiObjectId(track.recordingId);
|
|
586
|
+
const reference = await getRecordingEngineSession(
|
|
587
|
+
client,
|
|
588
|
+
recordingId,
|
|
589
|
+
input.recordingEngineSessionPackageId,
|
|
590
|
+
);
|
|
591
|
+
if (!reference) throw new Error("Recording has no canonical engine session");
|
|
592
|
+
return {
|
|
593
|
+
releaseId,
|
|
594
|
+
recordingId,
|
|
595
|
+
sessionBlobId: walrusBlobIdFromU256(reference.blobId),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ── Recording extension writes ──────────────────────────────────────────────
|
|
600
|
+
|
|
601
|
+
export interface WriteRecordingEngineSessionParams {
|
|
602
|
+
readonly recordingId: ObjectInput;
|
|
603
|
+
readonly authority: AdminCapAuthority;
|
|
604
|
+
readonly recordingShareType: string;
|
|
605
|
+
readonly compositionShareType: string;
|
|
606
|
+
readonly sessionBlobId: bigint | string;
|
|
607
|
+
readonly oriPackageId: string;
|
|
608
|
+
readonly recordingEngineSessionPackageId: string;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function attachRecordingEngineSession(
|
|
612
|
+
params: WriteRecordingEngineSessionParams,
|
|
613
|
+
): TxThunk {
|
|
614
|
+
return writeRecordingEngineSession("attach_engine_session", params);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function replaceRecordingEngineSession(
|
|
618
|
+
params: WriteRecordingEngineSessionParams,
|
|
619
|
+
): TxThunk {
|
|
620
|
+
return writeRecordingEngineSession("replace_engine_session", params);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function unsetRecordingEngineSession(
|
|
624
|
+
params: Omit<WriteRecordingEngineSessionParams, "sessionBlobId" | "oriPackageId">,
|
|
625
|
+
): TxThunk {
|
|
626
|
+
return (tx) => {
|
|
627
|
+
invokeWithAdminCap(tx, params.authority, {
|
|
628
|
+
target: `${params.recordingEngineSessionPackageId}::recording_engine_session::unset_engine_session`,
|
|
629
|
+
typeArguments: [params.recordingShareType, params.compositionShareType],
|
|
630
|
+
arguments: [object(tx, params.recordingId)],
|
|
631
|
+
adminCapIndex: 1,
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function writeRecordingEngineSession(
|
|
637
|
+
fn: "attach_engine_session" | "replace_engine_session",
|
|
638
|
+
params: WriteRecordingEngineSessionParams,
|
|
639
|
+
): TxThunk {
|
|
640
|
+
const blob = u256("sessionBlobId", params.sessionBlobId);
|
|
641
|
+
return (tx) => {
|
|
642
|
+
const reference = tx.moveCall({
|
|
643
|
+
target: `${params.oriPackageId}::walrus_data::new_blob`,
|
|
644
|
+
arguments: [tx.pure.u256(blob)],
|
|
645
|
+
});
|
|
646
|
+
invokeWithAdminCap(tx, params.authority, {
|
|
647
|
+
target: `${params.recordingEngineSessionPackageId}::recording_engine_session::${fn}`,
|
|
648
|
+
typeArguments: [params.recordingShareType, params.compositionShareType],
|
|
649
|
+
arguments: [object(tx, params.recordingId), reference],
|
|
650
|
+
adminCapIndex: 1,
|
|
651
|
+
});
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function object(tx: Transaction, input: ObjectInput): TransactionObjectArgument {
|
|
656
|
+
return typeof input === "string" ? tx.object(input) : input;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ── Binary and validation helpers ───────────────────────────────────────────
|
|
660
|
+
|
|
661
|
+
export function walrusBlobIdFromU256(value: bigint | string): string {
|
|
662
|
+
let remaining = u256("Walrus blob id", value);
|
|
663
|
+
const bytes = new Uint8Array(32);
|
|
664
|
+
for (let index = 0; index < 32; index += 1) {
|
|
665
|
+
bytes[index] = Number(remaining & 0xffn);
|
|
666
|
+
remaining >>= 8n;
|
|
667
|
+
}
|
|
668
|
+
return bytesToBase64Url(bytes);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
672
|
+
const alphabet =
|
|
673
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
674
|
+
let output = "";
|
|
675
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
676
|
+
const a = bytes[index] ?? 0;
|
|
677
|
+
const b = bytes[index + 1] ?? 0;
|
|
678
|
+
const c = bytes[index + 2] ?? 0;
|
|
679
|
+
output += alphabet[a >>> 2];
|
|
680
|
+
output += alphabet[((a & 3) << 4) | (b >>> 4)];
|
|
681
|
+
if (index + 1 < bytes.length) {
|
|
682
|
+
output += alphabet[((b & 15) << 2) | (c >>> 6)];
|
|
683
|
+
}
|
|
684
|
+
if (index + 2 < bytes.length) output += alphabet[c & 63];
|
|
685
|
+
}
|
|
686
|
+
return output;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export function base64UrlToBytes(value: string): Uint8Array {
|
|
690
|
+
if (
|
|
691
|
+
typeof value !== "string" ||
|
|
692
|
+
value.length % 4 === 1 ||
|
|
693
|
+
value.includes("=") ||
|
|
694
|
+
!/^[A-Za-z0-9_-]*$/.test(value)
|
|
695
|
+
) {
|
|
696
|
+
throw new Error("invalid canonical base64url");
|
|
697
|
+
}
|
|
698
|
+
const alphabet =
|
|
699
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
700
|
+
const bytes = new Uint8Array(Math.floor((value.length * 6) / 8));
|
|
701
|
+
let accumulator = 0;
|
|
702
|
+
let bits = 0;
|
|
703
|
+
let outputIndex = 0;
|
|
704
|
+
for (const character of value) {
|
|
705
|
+
const digit = alphabet.indexOf(character);
|
|
706
|
+
if (digit < 0) throw new Error("invalid canonical base64url");
|
|
707
|
+
accumulator = (accumulator << 6) | digit;
|
|
708
|
+
bits += 6;
|
|
709
|
+
if (bits >= 8) {
|
|
710
|
+
bits -= 8;
|
|
711
|
+
bytes[outputIndex] = (accumulator >>> bits) & 0xff;
|
|
712
|
+
outputIndex += 1;
|
|
713
|
+
accumulator &= (1 << bits) - 1;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (bytesToBase64Url(bytes) !== value) {
|
|
717
|
+
throw new Error("invalid canonical base64url padding bits");
|
|
718
|
+
}
|
|
719
|
+
return bytes;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
723
|
+
let value = "0x";
|
|
724
|
+
for (const byte of bytes) value += byte.toString(16).padStart(2, "0");
|
|
725
|
+
return value;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export function hexToBytes(
|
|
729
|
+
value: string,
|
|
730
|
+
expectedBytes?: number,
|
|
731
|
+
label = "hex",
|
|
732
|
+
): Uint8Array {
|
|
733
|
+
if (typeof value !== "string" || !/^0x(?:[0-9a-f]{2})*$/.test(value)) {
|
|
734
|
+
throw new Error(`${label} must be canonical lowercase 0x-prefixed hex`);
|
|
735
|
+
}
|
|
736
|
+
const bytes = new Uint8Array((value.length - 2) / 2);
|
|
737
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
738
|
+
bytes[index] = Number.parseInt(value.slice(2 + index * 2, 4 + index * 2), 16);
|
|
739
|
+
}
|
|
740
|
+
if (expectedBytes !== undefined && bytes.length !== expectedBytes) {
|
|
741
|
+
throw new Error(`${label} must encode exactly ${expectedBytes} bytes`);
|
|
742
|
+
}
|
|
743
|
+
return bytes;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function addressBytes(label: string, value: string): Uint8Array {
|
|
747
|
+
address(label, value);
|
|
748
|
+
return hexToBytes(value, 32, label);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function bytesToAddress(bytes: Uint8Array): string {
|
|
752
|
+
return bytesToHex(bytes);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function concatBytes(...parts: readonly Uint8Array[]): Uint8Array {
|
|
756
|
+
const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
757
|
+
let offset = 0;
|
|
758
|
+
for (const part of parts) {
|
|
759
|
+
result.set(part, offset);
|
|
760
|
+
offset += part.length;
|
|
761
|
+
}
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function exact(value: unknown, label: string, keys: readonly string[]): Record<string, unknown> {
|
|
766
|
+
if (
|
|
767
|
+
value === null ||
|
|
768
|
+
typeof value !== "object" ||
|
|
769
|
+
Array.isArray(value) ||
|
|
770
|
+
Object.getPrototypeOf(value) !== Object.prototype
|
|
771
|
+
) {
|
|
772
|
+
throw new Error(`${label} must be a plain object`);
|
|
773
|
+
}
|
|
774
|
+
const actual = Object.keys(value as object).sort();
|
|
775
|
+
const expected = [...keys].sort();
|
|
776
|
+
if (
|
|
777
|
+
actual.length !== expected.length ||
|
|
778
|
+
actual.some((key, index) => key !== expected[index])
|
|
779
|
+
) {
|
|
780
|
+
throw new Error(`${label} must contain exactly: ${expected.join(", ")}`);
|
|
781
|
+
}
|
|
782
|
+
return value as Record<string, unknown>;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function literal(label: string, value: unknown, expected: string | number): void {
|
|
786
|
+
if (value !== expected) throw new Error(`${label} must be ${JSON.stringify(expected)}`);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function text(label: string, value: unknown): asserts value is string {
|
|
790
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 256) {
|
|
791
|
+
throw new Error(`${label} must be 1..256 characters`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function address(label: string, value: unknown): asserts value is string {
|
|
796
|
+
if (typeof value !== "string" || !ADDRESS_RE.test(value)) {
|
|
797
|
+
throw new Error(`${label} must be a canonical 32-byte Sui address`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function sha256(label: string, value: unknown): asserts value is string {
|
|
802
|
+
if (typeof value !== "string" || !SHA256_RE.test(value)) {
|
|
803
|
+
throw new Error(`${label} must be a lowercase SHA-256 digest`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function blobId(label: string, value: unknown): asserts value is string {
|
|
808
|
+
if (typeof value !== "string" || !BLOB_ID_RE.test(value)) {
|
|
809
|
+
throw new Error(`${label} must be a canonical Walrus blob id`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function integer(
|
|
814
|
+
label: string,
|
|
815
|
+
value: unknown,
|
|
816
|
+
minimum: number,
|
|
817
|
+
maximum: number,
|
|
818
|
+
): asserts value is number {
|
|
819
|
+
if (
|
|
820
|
+
typeof value !== "number" ||
|
|
821
|
+
!Number.isSafeInteger(value) ||
|
|
822
|
+
value < minimum ||
|
|
823
|
+
value > maximum
|
|
824
|
+
) {
|
|
825
|
+
throw new Error(`${label} must be an integer in ${minimum}..${maximum}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function flag(label: string, value: unknown): asserts value is boolean {
|
|
830
|
+
if (typeof value !== "boolean") throw new Error(`${label} must be boolean`);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function finite(label: string, value: unknown): asserts value is number {
|
|
834
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
835
|
+
throw new Error(`${label} must be finite`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function assertPreset(value: unknown, label: string): void {
|
|
840
|
+
const preset = exact(value, label, [
|
|
841
|
+
"faderDb",
|
|
842
|
+
"pan",
|
|
843
|
+
"muted",
|
|
844
|
+
"soloed",
|
|
845
|
+
"effects",
|
|
846
|
+
]);
|
|
847
|
+
finite(`${label}.faderDb`, preset.faderDb);
|
|
848
|
+
finite(`${label}.pan`, preset.pan);
|
|
849
|
+
flag(`${label}.muted`, preset.muted);
|
|
850
|
+
flag(`${label}.soloed`, preset.soloed);
|
|
851
|
+
if (!Array.isArray(preset.effects) || preset.effects.length > 64) {
|
|
852
|
+
throw new Error(`${label}.effects must contain at most 64 entries`);
|
|
853
|
+
}
|
|
854
|
+
for (const [index, raw] of preset.effects.entries()) {
|
|
855
|
+
const effect = exact(raw, `${label}.effects[${index}]`, [
|
|
856
|
+
"slot",
|
|
857
|
+
"bypassed",
|
|
858
|
+
"params",
|
|
859
|
+
]);
|
|
860
|
+
integer(`${label}.effects[${index}].slot`, effect.slot, 0, MAX_U32);
|
|
861
|
+
flag(`${label}.effects[${index}].bypassed`, effect.bypassed);
|
|
862
|
+
const params = exactOpenObject(effect.params, `${label}.effects[${index}].params`);
|
|
863
|
+
for (const [key, parameter] of Object.entries(params)) {
|
|
864
|
+
if (!PARAM_KEY_RE.test(key)) throw new Error(`${label} parameter key ${key} is invalid`);
|
|
865
|
+
finite(`${label} parameter ${key}`, parameter);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function exactOpenObject(value: unknown, label: string): Record<string, unknown> {
|
|
871
|
+
if (
|
|
872
|
+
value === null ||
|
|
873
|
+
typeof value !== "object" ||
|
|
874
|
+
Array.isArray(value) ||
|
|
875
|
+
Object.getPrototypeOf(value) !== Object.prototype
|
|
876
|
+
) {
|
|
877
|
+
throw new Error(`${label} must be a plain object`);
|
|
878
|
+
}
|
|
879
|
+
return value as Record<string, unknown>;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function u256(label: string, value: bigint | string): bigint {
|
|
883
|
+
let parsed: bigint;
|
|
884
|
+
try {
|
|
885
|
+
parsed = typeof value === "bigint" ? value : BigInt(value);
|
|
886
|
+
} catch {
|
|
887
|
+
throw new Error(`${label} must be an exact u256 integer`);
|
|
888
|
+
}
|
|
889
|
+
if (parsed < 0n || parsed >= 1n << 256n) {
|
|
890
|
+
throw new Error(`${label} is outside u256 range`);
|
|
891
|
+
}
|
|
892
|
+
return parsed;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function canonicalJson(value: unknown): string {
|
|
896
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
897
|
+
return JSON.stringify(value);
|
|
898
|
+
}
|
|
899
|
+
if (typeof value === "number") {
|
|
900
|
+
if (!Number.isFinite(value)) throw new Error("canonical JSON forbids non-finite numbers");
|
|
901
|
+
return JSON.stringify(value);
|
|
902
|
+
}
|
|
903
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
904
|
+
if (typeof value !== "object") throw new Error("canonical JSON contains an unsupported value");
|
|
905
|
+
const object = value as Record<string, unknown>;
|
|
906
|
+
return `{${Object.keys(object)
|
|
907
|
+
.sort()
|
|
908
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`)
|
|
909
|
+
.join(",")}}`;
|
|
910
|
+
}
|