diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/ConfigTransforms.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/ConfigTransforms.scala index 01549c4d28..7126c5e2fe 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/ConfigTransforms.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/ConfigTransforms.scala @@ -200,6 +200,7 @@ object ConfigTransforms { updateAllAutomationConfigs( _.copy(rewardOperationRoundsCloseBufferDuration = NonNegativeFiniteDuration.ofMillis(100)) ), + disableDevelopmentFund(), ) } @@ -299,6 +300,9 @@ object ConfigTransforms { def disableZeroFees(): ConfigTransform = updateAllSvAppFoundDsoConfigs_(c => c.copy(zeroTransferFees = false)) + def disableDevelopmentFund(): ConfigTransform = + updateAllSvAppFoundDsoConfigs_(c => c.copy(developmentFundPercentage = Some(0.0))) + def updateAllValidatorAppConfigs( update: (String, ValidatorAppBackendConfig) => ValidatorAppBackendConfig ): ConfigTransform = @@ -718,6 +722,9 @@ object ConfigTransforms { } } + def withDevelopmentFundPercentage(percentage: BigDecimal): ConfigTransform = + updateAllSvAppFoundDsoConfigs_(c => c.copy(developmentFundPercentage = Some(percentage))) + private def portTransform(bump: Int, c: AdminServerConfig): AdminServerConfig = c.copy(internalPort = c.internalPort.map(_ + bump)) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala index 07ea6d7b20..d386eb2142 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala @@ -258,6 +258,7 @@ class AppUpgradeIntegrationTest ), java.util.Optional.empty(), java.util.Optional.empty(), + java.util.Optional.empty(), ) val upgradeAction = new ARC_AmuletRules( new CRARC_SetConfig( diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala index 61baa81af7..d5f0eaeb1d 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala @@ -229,6 +229,7 @@ class BootstrapPackageConfigIntegrationTest extends IntegrationTest with Splitwe ), java.util.Optional.empty(), java.util.Optional.empty(), + java.util.Optional.empty(), ) val upgradeAction = new ARC_AmuletRules( @@ -368,6 +369,7 @@ class BootstrapPackageConfigIntegrationTest extends IntegrationTest with Splitwe amuletConfig.packageConfig, java.util.Optional.empty(), java.util.Optional.empty(), + java.util.Optional.empty(), ) val upgradeAction = new ARC_AmuletRules( diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala new file mode 100644 index 0000000000..a06485c777 --- /dev/null +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala @@ -0,0 +1,99 @@ +package org.lfdecentralizedtrust.splice.integration.tests + +import com.digitalasset.canton.config.NonNegativeFiniteDuration +import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.UnclaimedDevelopmentFundCoupon +import org.lfdecentralizedtrust.splice.config.ConfigTransforms +import org.lfdecentralizedtrust.splice.config.ConfigTransforms.{ + ConfigurableApp, + updateAutomationConfig, +} +import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition +import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.AdvanceOpenMiningRoundTrigger +import org.lfdecentralizedtrust.splice.util.{TriggerTestUtil, WalletTestUtil} + +@org.lfdecentralizedtrust.splice.util.scalatesttags.SpliceDsoGovernance_0_1_21 +class DevelopmentFundCouponIntegrationTest + extends SvIntegrationTestBase + with TriggerTestUtil + with WalletTestUtil { + + private val threshold = 3 + + override def environmentDefinition + : org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition = + EnvironmentDefinition + .simpleTopology1Sv(this.getClass.getSimpleName) + .addConfigTransforms((_, config) => + updateAutomationConfig(ConfigurableApp.Sv)( + _.withPausedTrigger[AdvanceOpenMiningRoundTrigger] + )(config) + ) + .addConfigTransform((_, config) => + ConfigTransforms.updateInitialTickDuration(NonNegativeFiniteDuration.ofMillis(500))(config) + ) + .addConfigTransforms((_, config) => + ConfigTransforms.updateAllSvAppConfigs_( + _.copy( + unclaimedDevelopmentFundCouponsThreshold = threshold + ) + )(config) + ) + .addConfigTransform((_, config) => + ConfigTransforms.withDevelopmentFundPercentage(0.05)(config) + ) + + "UnclaimedDevelopmentFundCoupons are merged" in { implicit env => + val (_, couponAmount) = actAndCheck( + "Advance 5 rounds", { + Range(0, 5).foreach(_ => advanceRoundsByOneTickViaAutomation()) + }, + )( + "5 UnclaimedDevelopmentFundCoupons are created, and the trigger does not merge the coupons, " + + "as it only acts when the number of coupons is ≥ 2 × threshold", + _ => { + val coupons = sv1Backend.participantClient.ledger_api_extensions.acs + .filterJava(UnclaimedDevelopmentFundCoupon.COMPANION)(dsoParty) + coupons.size shouldBe 5 + coupons.head.data.amount + }, + ) + + actAndCheck( + "Advance one round to create one more UnclaimedDevelopmentFundCoupon, reaching 2 × threshold coupons", + advanceRoundsByOneTickViaAutomation(), + )( + "The MergeUnclaimedDevelopmentFundCouponsTrigger is triggered and merges the smallest three coupons (threshold), " + + "while keeping the remaining coupons unchanged.", + _ => { + sv1Backend.participantClient.ledger_api_extensions.acs + .filterJava(UnclaimedDevelopmentFundCoupon.COMPANION)(dsoParty) + .map(_.data.amount) + .sorted shouldBe Seq( + couponAmount, + couponAmount, + couponAmount, + couponAmount.multiply(new java.math.BigDecimal(3)), + ) + }, + ) + + actAndCheck( + "Advance two rounds to create two more UnclaimedDevelopmentFundCoupon, " + + "reaching 2 × threshold coupons and triggering a second merge", + Range(0, 2).foreach(_ => advanceRoundsByOneTickViaAutomation()), + )( + "The MergeUnclaimedDevelopmentFundCouponsTrigger merges the `threshold` smallest coupons", + _ => { + sv1Backend.participantClient.ledger_api_extensions.acs + .filterJava(UnclaimedDevelopmentFundCoupon.COMPANION)(dsoParty) + .map(_.data.amount) + .sorted shouldBe Seq( + couponAmount, + couponAmount, + couponAmount.multiply(new java.math.BigDecimal(3)), + couponAmount.multiply(new java.math.BigDecimal(3)), + ) + }, + ) + } +} diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DisabledWalletTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DisabledWalletTimeBasedIntegrationTest.scala index ac9890a644..478378946d 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DisabledWalletTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DisabledWalletTimeBasedIntegrationTest.scala @@ -68,7 +68,7 @@ class DisabledWalletTimeBasedIntegrationTest val expectedMinAmount = BigDecimal( computeSvRewardInRound0( - defaultIssuanceCurve.initialValue, + defaultIssuanceCurve().initialValue, defaultTickDuration, dsoSize = 1, ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanWithGradualStartsTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanWithGradualStartsTimeBasedIntegrationTest.scala index d3487d4b62..2e730e1ec2 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanWithGradualStartsTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanWithGradualStartsTimeBasedIntegrationTest.scala @@ -143,7 +143,7 @@ class ScanWithGradualStartsTimeBasedIntegrationTest val svRewardPerRound = BigDecimal( computeSvRewardInRound0( - defaultIssuanceCurve.initialValue, + defaultIssuanceCurve().initialValue, defaultTickDuration, dsoSize = 2, ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvReconcileSynchronizerConfigIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvReconcileSynchronizerConfigIntegrationTest.scala index 48f9dd9989..90c8f4d9bb 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvReconcileSynchronizerConfigIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvReconcileSynchronizerConfigIntegrationTest.scala @@ -141,6 +141,7 @@ class SvReconcileSynchronizerConfigIntegrationTest extends SvIntegrationTestBase amuletConfig.packageConfig, java.util.Optional.empty(), java.util.Optional.empty(), + java.util.Optional.empty(), ) } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala index 8d2c88602d..d0475b025c 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala @@ -487,6 +487,7 @@ class SvStateManagementIntegrationTest extends SvIntegrationTestBase with Trigge initialConfig.packageConfig, java.util.Optional.empty(), java.util.Optional.empty(), + java.util.Optional.empty(), ) val (_, voteRequestCid) = actAndCheck( diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedRewardCouponIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedRewardCouponIntegrationTest.scala index c0f8819ef5..09d1e6773f 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedRewardCouponIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedRewardCouponIntegrationTest.scala @@ -170,7 +170,7 @@ class SvTimeBasedRewardCouponIntegrationTest } val eachSvGetInRound0 = - computeSvRewardInRound0(defaultIssuanceCurve.initialValue, defaultTickDuration, svs.size) + computeSvRewardInRound0(defaultIssuanceCurve().initialValue, defaultTickDuration, svs.size) val sv1Party = sv1Backend.getDsoInfo().svParty val aliceValidatorParty = aliceValidatorBackend.getValidatorPartyId() val expectedAliceAmount = eachSvGetInRound0.multiply(new java.math.BigDecimal("0.3333")) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/AmuletConfigUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/AmuletConfigUtil.scala index 7e7727c5fd..c823c5c28d 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/AmuletConfigUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/AmuletConfigUtil.scala @@ -64,6 +64,7 @@ trait AmuletConfigUtil extends TestCommon { existingAmuletConfig.packageConfig, existingAmuletConfig.transferPreapprovalFee, existingAmuletConfig.featuredAppActivityMarkerAmount, + existingAmuletConfig.optDevelopmentFundManager, ) } diff --git a/apps/common/frontend-test-handlers/src/mocks/helpers/amulet-config-helper.ts b/apps/common/frontend-test-handlers/src/mocks/helpers/amulet-config-helper.ts index c898896538..f829f9abdc 100644 --- a/apps/common/frontend-test-handlers/src/mocks/helpers/amulet-config-helper.ts +++ b/apps/common/frontend-test-handlers/src/mocks/helpers/amulet-config-helper.ts @@ -158,6 +158,7 @@ export function getAmuletRulesConfig( amuletToIssuePerYear: '40000000000.0', validatorRewardCap: '0.2', optValidatorFaucetCap: '2.85', + optDevelopmentFundPercentage: '0.05', }, futureValues: [ { @@ -172,6 +173,7 @@ export function getAmuletRulesConfig( amuletToIssuePerYear: '20000000000.0', validatorRewardCap: '0.2', optValidatorFaucetCap: '2.85', + optDevelopmentFundPercentage: '0.05', }, }, { @@ -186,6 +188,7 @@ export function getAmuletRulesConfig( amuletToIssuePerYear: '10000000000.0', validatorRewardCap: '0.2', optValidatorFaucetCap: '2.85', + optDevelopmentFundPercentage: '0.05', }, }, { @@ -200,6 +203,7 @@ export function getAmuletRulesConfig( amuletToIssuePerYear: '5000000000.0', validatorRewardCap: '0.2', optValidatorFaucetCap: '2.85', + optDevelopmentFundPercentage: '0.05', }, }, { @@ -214,12 +218,14 @@ export function getAmuletRulesConfig( amuletToIssuePerYear: '2500000000.0', validatorRewardCap: '0.2', optValidatorFaucetCap: '2.85', + optDevelopmentFundPercentage: '0.05', }, }, ], }, transferPreapprovalFee: null, featuredAppActivityMarkerAmount: null, + optDevelopmentFundManager: null, }; } @@ -306,7 +312,8 @@ export function getExpectedAmuletRulesConfigDiffsHTML( "validatorRewardCap": "0.2", "featuredAppRewardCap": "100.0", "unfeaturedAppRewardCap": "0.6", - "optValidatorFaucetCap": "2.85" + "optValidatorFaucetCap": "2.85", + "optDevelopmentFundPercentage": "0.05" }, "futureValues": [ { @@ -320,7 +327,8 @@ export function getExpectedAmuletRulesConfigDiffsHTML( "validatorRewardCap": "0.2", "featuredAppRewardCap": "100.0", "unfeaturedAppRewardCap": "0.6", - "optValidatorFaucetCap": "2.85" + "optValidatorFaucetCap": "2.85", + "optDevelopmentFundPercentage": "0.05" } }, { @@ -334,7 +342,8 @@ export function getExpectedAmuletRulesConfigDiffsHTML( "validatorRewardCap": "0.2", "featuredAppRewardCap": "100.0", "unfeaturedAppRewardCap": "0.6", - "optValidatorFaucetCap": "2.85" + "optValidatorFaucetCap": "2.85", + "optDevelopmentFundPercentage": "0.05" } }, { @@ -348,7 +357,8 @@ export function getExpectedAmuletRulesConfigDiffsHTML( "validatorRewardCap": "0.2", "featuredAppRewardCap": "100.0", "unfeaturedAppRewardCap": "0.6", - "optValidatorFaucetCap": "2.85" + "optValidatorFaucetCap": "2.85", + "optDevelopmentFundPercentage": "0.05" } }, { @@ -362,12 +372,16 @@ export function getExpectedAmuletRulesConfigDiffsHTML( "validatorRewardCap": "0.2", "featuredAppRewardCap": "100.0", "unfeaturedAppRewardCap": "0.6", - "optValidatorFaucetCap": "2.85" + "optValidatorFaucetCap": "2.85", + "optDevelopmentFundPercentage": "0.05" } } ] -}
  • optDevelopmentFundManager
    null
  • packageConfig
    {
       "amulet": "0.1.8",
    diff --git a/apps/common/frontend/src/__tests__/mocks/constants.ts b/apps/common/frontend/src/__tests__/mocks/constants.ts
    index 771235dc75..10b02c36ab 100644
    --- a/apps/common/frontend/src/__tests__/mocks/constants.ts
    +++ b/apps/common/frontend/src/__tests__/mocks/constants.ts
    @@ -90,6 +90,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                         featuredAppRewardCap: '100.0',
                         unfeaturedAppRewardCap: '0.6',
                         optValidatorFaucetCap: '2.85',
    +                    optDevelopmentFundPercentage: '0.05',
                       },
                       futureValues: [
                         {
    @@ -104,6 +105,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                             featuredAppRewardCap: '100.0',
                             unfeaturedAppRewardCap: '0.6',
                             optValidatorFaucetCap: '2.85',
    +                        optDevelopmentFundPercentage: '0.05',
                           },
                         },
                         {
    @@ -118,6 +120,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                             featuredAppRewardCap: '100.0',
                             unfeaturedAppRewardCap: '0.6',
                             optValidatorFaucetCap: '2.85',
    +                        optDevelopmentFundPercentage: '0.05',
                           },
                         },
                         {
    @@ -132,6 +135,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                             featuredAppRewardCap: '100.0',
                             unfeaturedAppRewardCap: '0.6',
                             optValidatorFaucetCap: '2.85',
    +                        optDevelopmentFundPercentage: '0.05',
                           },
                         },
                         {
    @@ -146,6 +150,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                             featuredAppRewardCap: '100.0',
                             unfeaturedAppRewardCap: '0.6',
                             optValidatorFaucetCap: '2.85',
    +                        optDevelopmentFundPercentage: '0.05',
                           },
                         },
                       ],
    @@ -186,6 +191,7 @@ export const plannedVoteResult: DsoRules_CloseVoteRequestResult = {
                     },
                     transferPreapprovalFee: null,
                     featuredAppActivityMarkerAmount: null,
    +                optDevelopmentFundManager: null,
                   },
                 },
               },
    diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala
    index 1c1fc789c3..9fa7e68b08 100644
    --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala
    +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala
    @@ -75,6 +75,7 @@ object DarResources {
       val amulet_0_1_12 = DarResource("splice-amulet-0.1.12.dar")
       val amulet_0_1_13 = DarResource("splice-amulet-0.1.13.dar")
       val amulet_0_1_14 = DarResource("splice-amulet-0.1.14.dar")
    +  val amulet_0_1_15 = DarResource("splice-amulet-0.1.15.dar")
       val amulet_current = DarResource("splice-amulet-current.dar")
       val amulet = PackageResource(
         amulet_current,
    @@ -95,6 +96,7 @@ object DarResources {
           amulet_0_1_12,
           amulet_0_1_13,
           amulet_0_1_14,
    +      amulet_0_1_15,
         ),
       )
     
    @@ -119,6 +121,7 @@ object DarResources {
       val dsoGovernance_0_1_18 = DarResource("splice-dso-governance-0.1.18.dar")
       val dsoGovernance_0_1_19 = DarResource("splice-dso-governance-0.1.19.dar")
       val dsoGovernance_0_1_20 = DarResource("splice-dso-governance-0.1.20.dar")
    +  val dsoGovernance_0_1_21 = DarResource("splice-dso-governance-0.1.21.dar")
       val dsoGovernance_current = DarResource("splice-dso-governance-current.dar")
       val dsoGovernance = PackageResource(
         dsoGovernance_current,
    @@ -145,6 +148,7 @@ object DarResources {
           dsoGovernance_0_1_18,
           dsoGovernance_0_1_19,
           dsoGovernance_0_1_20,
    +      dsoGovernance_0_1_21,
         ),
       )
     
    @@ -164,6 +168,7 @@ object DarResources {
       val amuletNameService_0_1_13 = DarResource("splice-amulet-name-service-0.1.13.dar")
       val amuletNameService_0_1_14 = DarResource("splice-amulet-name-service-0.1.14.dar")
       val amuletNameService_0_1_15 = DarResource("splice-amulet-name-service-0.1.15.dar")
    +  val amuletNameService_0_1_16 = DarResource("splice-amulet-name-service-0.1.16.dar")
       val amuletNameService_current = DarResource("splice-amulet-name-service-current.dar")
       val amuletNameService = PackageResource(
         amuletNameService_current,
    @@ -185,6 +190,7 @@ object DarResources {
           amuletNameService_0_1_13,
           amuletNameService_0_1_14,
           amuletNameService_0_1_15,
    +      amuletNameService_0_1_16,
         ),
       )
     
    @@ -203,6 +209,7 @@ object DarResources {
       val splitwell_0_1_12 = DarResource("splitwell-0.1.12.dar")
       val splitwell_0_1_13 = DarResource("splitwell-0.1.13.dar")
       val splitwell_0_1_14 = DarResource("splitwell-0.1.14.dar")
    +  val splitwell_0_1_15 = DarResource("splitwell-0.1.15.dar")
       val splitwell_current = DarResource("splitwell-current.dar")
       val splitwell = PackageResource(
         splitwell_current,
    @@ -223,6 +230,7 @@ object DarResources {
           splitwell_0_1_12,
           splitwell_0_1_13,
           splitwell_0_1_14,
    +      splitwell_0_1_15,
         ),
       )
     
    @@ -241,6 +249,7 @@ object DarResources {
       val wallet_0_1_12 = DarResource("splice-wallet-0.1.12.dar")
       val wallet_0_1_13 = DarResource("splice-wallet-0.1.13.dar")
       val wallet_0_1_14 = DarResource("splice-wallet-0.1.14.dar")
    +  val wallet_0_1_15 = DarResource("splice-wallet-0.1.15.dar")
       val wallet_current = DarResource("splice-wallet-current.dar")
       val wallet = PackageResource(
         wallet_current,
    @@ -261,6 +270,7 @@ object DarResources {
           wallet_0_1_12,
           wallet_0_1_13,
           wallet_0_1_14,
    +      wallet_0_1_15,
         ),
       )
     
    @@ -279,6 +289,7 @@ object DarResources {
       val walletPayments_0_1_12 = DarResource("splice-wallet-payments-0.1.12.dar")
       val walletPayments_0_1_13 = DarResource("splice-wallet-payments-0.1.13.dar")
       val walletPayments_0_1_14 = DarResource("splice-wallet-payments-0.1.14.dar")
    +  val walletPayments_0_1_15 = DarResource("splice-wallet-payments-0.1.15.dar")
       val walletPayments_current = DarResource("splice-wallet-payments-current.dar")
       val walletPayments = PackageResource(
         walletPayments_current,
    @@ -299,6 +310,7 @@ object DarResources {
           walletPayments_0_1_12,
           walletPayments_0_1_13,
           walletPayments_0_1_14,
    +      walletPayments_0_1_15,
         ),
       )
     
    @@ -308,6 +320,7 @@ object DarResources {
       val validatorLifecycle_0_1_3 = DarResource("splice-validator-lifecycle-0.1.3.dar")
       val validatorLifecycle_0_1_4 = DarResource("splice-validator-lifecycle-0.1.4.dar")
       val validatorLifecycle_0_1_5 = DarResource("splice-validator-lifecycle-0.1.5.dar")
    +  val validatorLifecycle_0_1_6 = DarResource("splice-validator-lifecycle-0.1.6.dar")
       val validatorLifecycle_current = DarResource("splice-validator-lifecycle-current.dar")
       val validatorLifecycle = PackageResource(
         validatorLifecycle_current,
    @@ -319,6 +332,7 @@ object DarResources {
           validatorLifecycle_0_1_3,
           validatorLifecycle_0_1_4,
           validatorLifecycle_0_1_5,
    +      validatorLifecycle_0_1_6,
         ),
       )
     
    diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/PackageVersionSupport.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/PackageVersionSupport.scala
    index a839a1609e..c626859876 100644
    --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/PackageVersionSupport.scala
    +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/PackageVersionSupport.scala
    @@ -108,6 +108,18 @@ trait PackageVersionSupport extends NamedLogging {
         )
       }
     
    +  def supportDevelopmentFund(parties: Seq[PartyId], now: CantonTimestamp)(implicit
    +      tc: TraceContext
    +  ): Future[FeatureSupport] = {
    +    isDarSupported(
    +      parties,
    +      PackageIdResolver.Package.SpliceDsoGovernance,
    +      now,
    +      DarResources.dsoGovernance,
    +      DarResources.dsoGovernance_0_1_21,
    +    )
    +  }
    +
       private def isDarSupported(
           parties: Seq[PartyId],
           packageId: PackageIdResolver.Package,
    diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceUtil.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceUtil.scala
    index 789e47683f..6e02ea8f39 100644
    --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceUtil.scala
    +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceUtil.scala
    @@ -207,6 +207,7 @@ object SpliceUtil {
           amuletsToIssuePerYear: Double,
           validatorPercentage: Double,
           appPercentage: Double,
    +      developmentFundPercentage: Option[BigDecimal] = None,
       ): splice.issuance.IssuanceConfig = new IssuanceConfig(
         damlDecimal(amuletsToIssuePerYear),
         damlDecimal(validatorPercentage),
    @@ -223,18 +224,29 @@ object SpliceUtil {
     
         // validatorFaucetCap
         Some(damlDecimal(2.85)).toJava,
    +
    +    // developmentFundPercentage
    +    developmentFundPercentage.map(damlDecimal).toJava,
       )
     
       private def hours(h: Long): RelTime = new RelTime(TimeUnit.HOURS.toMicros(h))
     
    -  val defaultIssuanceCurve: splice.schedule.Schedule[RelTime, IssuanceConfig] =
    +  def defaultIssuanceCurve(
    +      developmentFundPercentage: Option[BigDecimal] = None
    +  ): splice.schedule.Schedule[RelTime, IssuanceConfig] =
         new Schedule(
    -      issuanceConfig(40e9, 0.05, 0.15),
    +      issuanceConfig(40e9, 0.05, 0.15, developmentFundPercentage),
           Seq(
    -        new Tuple2(hours(365 * 12), issuanceConfig(20e9, 0.12, 0.4)),
    -        new Tuple2(hours(3 * 365 * 12), issuanceConfig(10e9, 0.18, 0.62)),
    -        new Tuple2(hours(5 * 365 * 24), issuanceConfig(5e9, 0.21, 0.69)),
    -        new Tuple2(hours(10 * 365 * 24), issuanceConfig(2.5e9, 0.20, 0.75)),
    +        new Tuple2(hours(365 * 12), issuanceConfig(20e9, 0.12, 0.4, developmentFundPercentage)),
    +        new Tuple2(
    +          hours(3 * 365 * 12),
    +          issuanceConfig(10e9, 0.18, 0.62, developmentFundPercentage),
    +        ),
    +        new Tuple2(hours(5 * 365 * 24), issuanceConfig(5e9, 0.21, 0.69, developmentFundPercentage)),
    +        new Tuple2(
    +          hours(10 * 365 * 24),
    +          issuanceConfig(2.5e9, 0.20, 0.75, developmentFundPercentage),
    +        ),
           ).asJava,
         )
     
    @@ -365,13 +377,14 @@ object SpliceUtil {
           transferPreapprovalFee: Option[BigDecimal] = None,
           featuredAppActivityMarkerAmount: Option[BigDecimal] = None,
           nextSynchronizerId: Option[SynchronizerId] = None,
    +      developmentFundPercentage: Option[BigDecimal] = None,
       ): splice.amuletconfig.AmuletConfig[splice.amuletconfig.USD] =
         new splice.amuletconfig.AmuletConfig(
           // transferConfig
           defaultTransferConfig(initialMaxNumInputs, holdingFee, zeroTransferFees = zeroTransferFees),
     
           // issuance curve
    -      defaultIssuanceCurve,
    +      defaultIssuanceCurve(developmentFundPercentage),
     
           // global domain config
           defaultDecentralizedSynchronizerConfig(
    @@ -389,6 +402,7 @@ object SpliceUtil {
           initialPackageConfig,
           transferPreapprovalFee.map(_.bigDecimal).toJava,
           featuredAppActivityMarkerAmount.map(_.bigDecimal).toJava,
    +      Optional.empty(),
         )
     
       def defaultAnsConfig(
    diff --git a/apps/common/src/test/java/org/lfdecentralizedtrust/splice/util/scalatesttags/SpliceDsoGovernance_0_1_21.java b/apps/common/src/test/java/org/lfdecentralizedtrust/splice/util/scalatesttags/SpliceDsoGovernance_0_1_21.java
    new file mode 100644
    index 0000000000..ac24eab842
    --- /dev/null
    +++ b/apps/common/src/test/java/org/lfdecentralizedtrust/splice/util/scalatesttags/SpliceDsoGovernance_0_1_21.java
    @@ -0,0 +1,14 @@
    +package org.lfdecentralizedtrust.splice.util.scalatesttags;
    +
    +import org.scalatest.TagAnnotation;
    +
    +import java.lang.annotation.ElementType;
    +import java.lang.annotation.Retention;
    +import java.lang.annotation.RetentionPolicy;
    +import java.lang.annotation.Target;
    +
    +// Don't run this test when testing against splice-dso-governance < 0.1.21
    +@TagAnnotation
    +@Retention(RetentionPolicy.RUNTIME)
    +@Target({ElementType.METHOD, ElementType.TYPE})
    +public @interface SpliceDsoGovernance_0_1_21 {}
    diff --git a/apps/package-lock.json b/apps/package-lock.json
    index 889a7e583d..3ea50e1ec5 100644
    --- a/apps/package-lock.json
    +++ b/apps/package-lock.json
    @@ -27,13 +27,13 @@
             "wallet/external-openapi-ts-client"
           ],
           "dependencies": {
    -        "@daml.js/ans": "file:common/frontend/daml.js/splice-amulet-name-service-0.1.15",
    -        "@daml.js/splice-amulet": "file:common/frontend/daml.js/splice-amulet-0.1.14",
    -        "@daml.js/splice-dso-governance": "file:common/frontend/daml.js/splice-dso-governance-0.1.20",
    -        "@daml.js/splice-validator-lifecycle": "file:common/frontend/daml.js/splice-validator-lifecycle-0.1.5",
    -        "@daml.js/splice-wallet": "file:common/frontend/daml.js/splice-wallet-0.1.14",
    -        "@daml.js/splice-wallet-payments": "file:common/frontend/daml.js/splice-wallet-payments-0.1.14",
    -        "@daml.js/splitwell": "file:common/frontend/daml.js/splitwell-0.1.14",
    +        "@daml.js/ans": "file:common/frontend/daml.js/splice-amulet-name-service-0.1.16",
    +        "@daml.js/splice-amulet": "file:common/frontend/daml.js/splice-amulet-0.1.15",
    +        "@daml.js/splice-dso-governance": "file:common/frontend/daml.js/splice-dso-governance-0.1.21",
    +        "@daml.js/splice-validator-lifecycle": "file:common/frontend/daml.js/splice-validator-lifecycle-0.1.6",
    +        "@daml.js/splice-wallet": "file:common/frontend/daml.js/splice-wallet-0.1.15",
    +        "@daml.js/splice-wallet-payments": "file:common/frontend/daml.js/splice-wallet-payments-0.1.15",
    +        "@daml.js/splitwell": "file:common/frontend/daml.js/splitwell-0.1.15",
             "xunit-viewer": "^10.6.1"
           }
         },
    @@ -320,6 +320,25 @@
         "common/frontend/daml.js/splice-amulet-0.1.14": {
           "name": "@daml.js/splice-amulet-0.1.14",
           "version": "0.0.0",
    +      "extraneous": true,
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Set-Types-1.0.0": "file:../daml-stdlib-DA-Set-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-api-featured-app-v1-1.0.0": "file:../splice-api-featured-app-v1-1.0.0",
    +        "@daml.js/splice-api-token-allocation-instruction-v1-1.0.0": "file:../splice-api-token-allocation-instruction-v1-1.0.0",
    +        "@daml.js/splice-api-token-allocation-v1-1.0.0": "file:../splice-api-token-allocation-v1-1.0.0",
    +        "@daml.js/splice-api-token-holding-v1-1.0.0": "file:../splice-api-token-holding-v1-1.0.0",
    +        "@daml.js/splice-api-token-metadata-v1-1.0.0": "file:../splice-api-token-metadata-v1-1.0.0",
    +        "@daml.js/splice-api-token-transfer-instruction-v1-1.0.0": "file:../splice-api-token-transfer-instruction-v1-1.0.0",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
    +    "common/frontend/daml.js/splice-amulet-0.1.15": {
    +      "name": "@daml.js/splice-amulet-0.1.15",
    +      "version": "0.0.0",
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    @@ -422,6 +441,7 @@
         "common/frontend/daml.js/splice-amulet-name-service-0.1.15": {
           "name": "@daml.js/splice-amulet-name-service-0.1.15",
           "version": "0.0.0",
    +      "extraneous": true,
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    @@ -432,6 +452,19 @@
             "@mojotech/json-type-validation": "^3.1.0"
           }
         },
    +    "common/frontend/daml.js/splice-amulet-name-service-0.1.16": {
    +      "name": "@daml.js/splice-amulet-name-service-0.1.16",
    +      "version": "0.0.0",
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-amulet-0.1.15": "file:../splice-amulet-0.1.15",
    +        "@daml.js/splice-api-featured-app-v1-1.0.0": "file:../splice-api-featured-app-v1-1.0.0",
    +        "@daml.js/splice-wallet-payments-0.1.15": "file:../splice-wallet-payments-0.1.15",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
         "common/frontend/daml.js/splice-amulet-name-service-0.1.9": {
           "name": "@daml.js/splice-amulet-name-service-0.1.9",
           "version": "0.0.0",
    @@ -555,6 +588,7 @@
         "common/frontend/daml.js/splice-dso-governance-0.1.20": {
           "name": "@daml.js/splice-dso-governance-0.1.20",
           "version": "0.0.0",
    +      "extraneous": true,
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    @@ -567,6 +601,21 @@
             "@mojotech/json-type-validation": "^3.1.0"
           }
         },
    +    "common/frontend/daml.js/splice-dso-governance-0.1.21": {
    +      "name": "@daml.js/splice-dso-governance-0.1.21",
    +      "version": "0.0.0",
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Set-Types-1.0.0": "file:../daml-stdlib-DA-Set-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-amulet-0.1.15": "file:../splice-amulet-0.1.15",
    +        "@daml.js/splice-amulet-name-service-0.1.16": "file:../splice-amulet-name-service-0.1.16",
    +        "@daml.js/splice-wallet-payments-0.1.15": "file:../splice-wallet-payments-0.1.15",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
         "common/frontend/daml.js/splice-validator-lifecycle-0.1.3": {
           "name": "@daml.js/splice-validator-lifecycle-0.1.3",
           "version": "0.0.0",
    @@ -590,6 +639,16 @@
         "common/frontend/daml.js/splice-validator-lifecycle-0.1.5": {
           "name": "@daml.js/splice-validator-lifecycle-0.1.5",
           "version": "0.0.0",
    +      "extraneous": true,
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
    +    "common/frontend/daml.js/splice-validator-lifecycle-0.1.6": {
    +      "name": "@daml.js/splice-validator-lifecycle-0.1.6",
    +      "version": "0.0.0",
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    @@ -667,6 +726,7 @@
         "common/frontend/daml.js/splice-wallet-0.1.14": {
           "name": "@daml.js/splice-wallet-0.1.14",
           "version": "0.0.0",
    +      "extraneous": true,
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    @@ -680,6 +740,22 @@
             "@mojotech/json-type-validation": "^3.1.0"
           }
         },
    +    "common/frontend/daml.js/splice-wallet-0.1.15": {
    +      "name": "@daml.js/splice-wallet-0.1.15",
    +      "version": "0.0.0",
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-amulet-0.1.15": "file:../splice-amulet-0.1.15",
    +        "@daml.js/splice-api-token-allocation-instruction-v1-1.0.0": "file:../splice-api-token-allocation-instruction-v1-1.0.0",
    +        "@daml.js/splice-api-token-allocation-v1-1.0.0": "file:../splice-api-token-allocation-v1-1.0.0",
    +        "@daml.js/splice-api-token-transfer-instruction-v1-1.0.0": "file:../splice-api-token-transfer-instruction-v1-1.0.0",
    +        "@daml.js/splice-wallet-payments-0.1.15": "file:../splice-wallet-payments-0.1.15",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
         "common/frontend/daml.js/splice-wallet-0.1.9": {
           "name": "@daml.js/splice-wallet-0.1.9",
           "version": "0.0.0",
    @@ -752,6 +828,7 @@
         "common/frontend/daml.js/splice-wallet-payments-0.1.14": {
           "name": "@daml.js/splice-wallet-payments-0.1.14",
           "version": "0.0.0",
    +      "extraneous": true,
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    @@ -761,6 +838,18 @@
             "@mojotech/json-type-validation": "^3.1.0"
           }
         },
    +    "common/frontend/daml.js/splice-wallet-payments-0.1.15": {
    +      "name": "@daml.js/splice-wallet-payments-0.1.15",
    +      "version": "0.0.0",
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-amulet-0.1.15": "file:../splice-amulet-0.1.15",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
         "common/frontend/daml.js/splice-wallet-payments-0.1.9": {
           "name": "@daml.js/splice-wallet-payments-0.1.9",
           "version": "0.0.0",
    @@ -833,6 +922,7 @@
         "common/frontend/daml.js/splitwell-0.1.14": {
           "name": "@daml.js/splitwell-0.1.14",
           "version": "0.0.0",
    +      "extraneous": true,
           "license": "UNLICENSED",
           "dependencies": {
             "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    @@ -843,6 +933,19 @@
             "@mojotech/json-type-validation": "^3.1.0"
           }
         },
    +    "common/frontend/daml.js/splitwell-0.1.15": {
    +      "name": "@daml.js/splitwell-0.1.15",
    +      "version": "0.0.0",
    +      "license": "UNLICENSED",
    +      "dependencies": {
    +        "@daml.js/daml-prim-DA-Types-1.0.0": "file:../daml-prim-DA-Types-1.0.0",
    +        "@daml.js/daml-stdlib-DA-Time-Types-1.0.0": "file:../daml-stdlib-DA-Time-Types-1.0.0",
    +        "@daml.js/ghc-stdlib-DA-Internal-Template-1.0.0": "file:../ghc-stdlib-DA-Internal-Template-1.0.0",
    +        "@daml.js/splice-amulet-0.1.15": "file:../splice-amulet-0.1.15",
    +        "@daml.js/splice-wallet-payments-0.1.15": "file:../splice-wallet-payments-0.1.15",
    +        "@mojotech/json-type-validation": "^3.1.0"
    +      }
    +    },
         "common/frontend/daml.js/splitwell-0.1.9": {
           "name": "@daml.js/splitwell-0.1.9",
           "version": "0.0.0",
    @@ -1312,7 +1415,7 @@
           }
         },
         "node_modules/@daml.js/ans": {
    -      "resolved": "common/frontend/daml.js/splice-amulet-name-service-0.1.15",
    +      "resolved": "common/frontend/daml.js/splice-amulet-name-service-0.1.16",
           "link": true
         },
         "node_modules/@daml.js/daml-prim-DA-Types-1.0.0": {
    @@ -1332,15 +1435,15 @@
           "link": true
         },
         "node_modules/@daml.js/splice-amulet": {
    -      "resolved": "common/frontend/daml.js/splice-amulet-0.1.14",
    +      "resolved": "common/frontend/daml.js/splice-amulet-0.1.15",
           "link": true
         },
    -    "node_modules/@daml.js/splice-amulet-0.1.14": {
    -      "resolved": "common/frontend/daml.js/splice-amulet-0.1.14",
    +    "node_modules/@daml.js/splice-amulet-0.1.15": {
    +      "resolved": "common/frontend/daml.js/splice-amulet-0.1.15",
           "link": true
         },
    -    "node_modules/@daml.js/splice-amulet-name-service-0.1.15": {
    -      "resolved": "common/frontend/daml.js/splice-amulet-name-service-0.1.15",
    +    "node_modules/@daml.js/splice-amulet-name-service-0.1.16": {
    +      "resolved": "common/frontend/daml.js/splice-amulet-name-service-0.1.16",
           "link": true
         },
         "node_modules/@daml.js/splice-api-featured-app-v1-1.0.0": {
    @@ -1380,27 +1483,27 @@
           "link": true
         },
         "node_modules/@daml.js/splice-dso-governance": {
    -      "resolved": "common/frontend/daml.js/splice-dso-governance-0.1.20",
    +      "resolved": "common/frontend/daml.js/splice-dso-governance-0.1.21",
           "link": true
         },
         "node_modules/@daml.js/splice-validator-lifecycle": {
    -      "resolved": "common/frontend/daml.js/splice-validator-lifecycle-0.1.5",
    +      "resolved": "common/frontend/daml.js/splice-validator-lifecycle-0.1.6",
           "link": true
         },
         "node_modules/@daml.js/splice-wallet": {
    -      "resolved": "common/frontend/daml.js/splice-wallet-0.1.14",
    +      "resolved": "common/frontend/daml.js/splice-wallet-0.1.15",
           "link": true
         },
         "node_modules/@daml.js/splice-wallet-payments": {
    -      "resolved": "common/frontend/daml.js/splice-wallet-payments-0.1.14",
    +      "resolved": "common/frontend/daml.js/splice-wallet-payments-0.1.15",
           "link": true
         },
    -    "node_modules/@daml.js/splice-wallet-payments-0.1.14": {
    -      "resolved": "common/frontend/daml.js/splice-wallet-payments-0.1.14",
    +    "node_modules/@daml.js/splice-wallet-payments-0.1.15": {
    +      "resolved": "common/frontend/daml.js/splice-wallet-payments-0.1.15",
           "link": true
         },
         "node_modules/@daml.js/splitwell": {
    -      "resolved": "common/frontend/daml.js/splitwell-0.1.14",
    +      "resolved": "common/frontend/daml.js/splitwell-0.1.15",
           "link": true
         },
         "node_modules/@daml/ledger": {
    diff --git a/apps/package.json b/apps/package.json
    index 238de993a0..4c3df22b70 100644
    --- a/apps/package.json
    +++ b/apps/package.json
    @@ -22,13 +22,13 @@
         "wallet/external-openapi-ts-client"
       ],
       "dependencies": {
    -    "@daml.js/ans": "file:common/frontend/daml.js/splice-amulet-name-service-0.1.15",
    -    "@daml.js/splice-amulet": "file:common/frontend/daml.js/splice-amulet-0.1.14",
    -    "@daml.js/splice-dso-governance": "file:common/frontend/daml.js/splice-dso-governance-0.1.20",
    -    "@daml.js/splice-validator-lifecycle": "file:common/frontend/daml.js/splice-validator-lifecycle-0.1.5",
    -    "@daml.js/splice-wallet": "file:common/frontend/daml.js/splice-wallet-0.1.14",
    -    "@daml.js/splice-wallet-payments": "file:common/frontend/daml.js/splice-wallet-payments-0.1.14",
    -    "@daml.js/splitwell": "file:common/frontend/daml.js/splitwell-0.1.14",
    +    "@daml.js/ans": "file:common/frontend/daml.js/splice-amulet-name-service-0.1.16",
    +    "@daml.js/splice-amulet": "file:common/frontend/daml.js/splice-amulet-0.1.15",
    +    "@daml.js/splice-dso-governance": "file:common/frontend/daml.js/splice-dso-governance-0.1.21",
    +    "@daml.js/splice-validator-lifecycle": "file:common/frontend/daml.js/splice-validator-lifecycle-0.1.6",
    +    "@daml.js/splice-wallet": "file:common/frontend/daml.js/splice-wallet-0.1.15",
    +    "@daml.js/splice-wallet-payments": "file:common/frontend/daml.js/splice-wallet-payments-0.1.15",
    +    "@daml.js/splitwell": "file:common/frontend/daml.js/splitwell-0.1.15",
         "xunit-viewer": "^10.6.1"
       }
     }
    diff --git a/apps/scan/frontend/src/__tests__/mocks/data.ts b/apps/scan/frontend/src/__tests__/mocks/data.ts
    index 372eabd1f0..4c74913dca 100644
    --- a/apps/scan/frontend/src/__tests__/mocks/data.ts
    +++ b/apps/scan/frontend/src/__tests__/mocks/data.ts
    @@ -73,6 +73,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
                 featuredAppRewardCap: '100.0',
                 validatorRewardCap: '0.2',
                 optValidatorFaucetCap: '2.85',
    +            optDevelopmentFundPercentage: '0.05',
               },
               futureValues: [
                 {
    @@ -87,6 +88,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
                     featuredAppRewardCap: '100.0',
                     validatorRewardCap: '0.2',
                     optValidatorFaucetCap: '2.85',
    +                optDevelopmentFundPercentage: '0.05',
                   },
                 },
                 {
    @@ -101,6 +103,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
                     featuredAppRewardCap: '100.0',
                     validatorRewardCap: '0.2',
                     optValidatorFaucetCap: '2.85',
    +                optDevelopmentFundPercentage: '0.05',
                   },
                 },
                 {
    @@ -115,6 +118,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
                     featuredAppRewardCap: '100.0',
                     validatorRewardCap: '0.2',
                     optValidatorFaucetCap: '2.85',
    +                optDevelopmentFundPercentage: '0.05',
                   },
                 },
                 {
    @@ -129,6 +133,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
                     featuredAppRewardCap: '100.0',
                     validatorRewardCap: '0.2',
                     optValidatorFaucetCap: '2.85',
    +                optDevelopmentFundPercentage: '0.05',
                   },
                 },
               ],
    @@ -170,6 +175,7 @@ export function amuletRules(zeroTransferFees: boolean): any {
             },
             transferPreapprovalFee: null,
             featuredAppActivityMarkerAmount: null,
    +        optDevelopmentFundManager: null,
           },
           futureValues: [],
         },
    diff --git a/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts b/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts
    index 4de1a82ef1..bdd91a2833 100644
    --- a/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts
    +++ b/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts
    @@ -83,6 +83,7 @@ export const buildScanMock = (baseScanUrl: string): RestHandler[] => {
                           amuletToIssuePerYear: '40000000000.0',
                           validatorRewardCap: '0.2',
                           optValidatorFaucetCap: '2.85',
    +                      optDevelopmentFundPercentage: '0.05',
                         },
                         opensAt: '2025-02-03T13:43:37.895871Z',
                         transferConfigUsd: {
    @@ -153,6 +154,7 @@ export const buildScanMock = (baseScanUrl: string): RestHandler[] => {
                           amuletToIssuePerYear: '40000000000.0',
                           validatorRewardCap: '0.2',
                           optValidatorFaucetCap: '2.85',
    +                      optDevelopmentFundPercentage: '0.05',
                         },
                         opensAt: '2025-02-03T13:54:09.430298Z',
                         transferConfigUsd: {
    @@ -223,6 +225,7 @@ export const buildScanMock = (baseScanUrl: string): RestHandler[] => {
                           amuletToIssuePerYear: '40000000000.0',
                           validatorRewardCap: '0.2',
                           optValidatorFaucetCap: '2.85',
    +                      optDevelopmentFundPercentage: '0.05',
                         },
                         opensAt: '2025-02-03T14:04:37.024279Z',
                         transferConfigUsd: {
    diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala
    index 4d22faf7f1..3affdbc8b1 100644
    --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala
    +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala
    @@ -1993,6 +1993,7 @@ trait AmuletTransferUtil { self: StoreTest =>
         // TODO(#968): track faucet coupon inputs separately
         java.util.Optional.empty(),
         java.util.Optional.empty(),
    +    java.util.Optional.empty(),
       )
     
       def mkTransferResult(
    diff --git a/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.tsx b/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.tsx
    index 3fb6066979..b03af7d36a 100644
    --- a/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.tsx
    +++ b/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.tsx
    @@ -128,6 +128,12 @@ describe('buildAmuletRulesConfigFromChanges', () => {
             currentValue: '50',
             newValue: '100',
           },
    +      {
    +        fieldName: 'issuanceCurveInitialValueOptDevelopmentFundPercentage',
    +        label: 'Opt Development Fund Percentage',
    +        currentValue: '0.05',
    +        newValue: '0.06',
    +      },
           {
             fieldName: 'decentralizedSynchronizerActiveSynchronizer',
             label: 'Active Synchronizer',
    @@ -237,6 +243,7 @@ describe('buildAmuletRulesConfigFromChanges', () => {
         expect(result.issuanceCurve.initialValue.featuredAppRewardCap).toBe('1000');
         expect(result.issuanceCurve.initialValue.unfeaturedAppRewardCap).toBe('200');
         expect(result.issuanceCurve.initialValue.optValidatorFaucetCap).toBe('100');
    +    expect(result.issuanceCurve.initialValue.optDevelopmentFundPercentage).toBe('0.06');
     
         expect(result.decentralizedSynchronizer.activeSynchronizer).toBe('sync2');
         const expectedRequiredSynchronizers = Array.from(
    @@ -351,6 +358,12 @@ describe('buildAmuletRulesConfigFromChanges', () => {
             currentValue: '50',
             newValue: '100',
           },
    +      {
    +        fieldName: 'issuanceCurveFutureValues0OptDevelopmentFundPercentage',
    +        label: 'Future Value 0 Opt Development Fund Percentage',
    +        currentValue: '0.05',
    +        newValue: '0.06',
    +      },
         ];
     
         const result = buildAmuletRulesConfigFromChanges(changes);
    @@ -366,6 +379,7 @@ describe('buildAmuletRulesConfigFromChanges', () => {
             featuredAppRewardCap: '1000',
             unfeaturedAppRewardCap: '200',
             optValidatorFaucetCap: '100',
    +        optDevelopmentFundPercentage: '0.06',
           },
         });
       });
    diff --git a/apps/sv/frontend/src/utils/buildAmuletConfigChanges.ts b/apps/sv/frontend/src/utils/buildAmuletConfigChanges.ts
    index afdb60053b..3666352b4a 100644
    --- a/apps/sv/frontend/src/utils/buildAmuletConfigChanges.ts
    +++ b/apps/sv/frontend/src/utils/buildAmuletConfigChanges.ts
    @@ -34,6 +34,12 @@ export function buildAmuletConfigChanges(
           currentValue: before?.featuredAppActivityMarkerAmount || '',
           newValue: after?.featuredAppActivityMarkerAmount || '',
         },
    +    {
    +      fieldName: 'optDevelopmentFundManager',
    +      label: 'Development Fund Manager',
    +      currentValue: before?.optDevelopmentFundManager || '',
    +      newValue: after?.optDevelopmentFundManager || '',
    +    },
         {
           fieldName: 'transferConfigCreateFee',
           label: 'Transfer (Create Fee)',
    @@ -227,6 +233,12 @@ function buildIssuanceCurveChanges(
           currentValue: before?.initialValue?.optValidatorFaucetCap || '',
           newValue: after?.initialValue?.optValidatorFaucetCap || '',
         },
    +    {
    +      fieldName: 'issuanceCurveInitialValueOptDevelopmentFundPercentage',
    +      label: 'Issuance Curve Initial Value (Development Fund Percentage)',
    +      currentValue: before?.initialValue?.optDevelopmentFundPercentage || '',
    +      newValue: after?.initialValue?.optDevelopmentFundPercentage || '',
    +    },
       ] as ConfigChange[];
     
       const futureValues =
    @@ -281,6 +293,12 @@ function buildIssuanceCurveChanges(
                 currentValue: fv._2.optValidatorFaucetCap || '',
                 newValue: after?.futureValues[idx]._2.optValidatorFaucetCap || '',
               },
    +          {
    +            fieldName: `issuanceCurveFutureValues${idx}OptDevelopmentFundPercentage`,
    +            label: `Issuance Curve Future Value (Development Fund Percentage) (${idx})`,
    +            currentValue: fv._2.optDevelopmentFundPercentage || '',
    +            newValue: after?.futureValues[idx]._2.optDevelopmentFundPercentage || '',
    +          },
             ] as ConfigChange[];
           })
           .flat() || [];
    diff --git a/apps/sv/frontend/src/utils/buildAmuletRulesConfigFromChanges.ts b/apps/sv/frontend/src/utils/buildAmuletRulesConfigFromChanges.ts
    index cc264c47d5..fbd7d55da5 100644
    --- a/apps/sv/frontend/src/utils/buildAmuletRulesConfigFromChanges.ts
    +++ b/apps/sv/frontend/src/utils/buildAmuletRulesConfigFromChanges.ts
    @@ -62,6 +62,9 @@ export function buildAmuletRulesConfigFromChanges(
       const futureValues: Tuple2[] = [];
       for (let i = 0; i < futureValuesCount; i++) {
         const time = { microseconds: getValue(`issuanceCurveFutureValues${i}`) };
    +    const futureOptDevelopmentFundPercentage = getValue(
    +      `issuanceCurveFutureValues${i}OptDevelopmentFundPercentage`
    +    );
         const config: IssuanceConfig = {
           amuletToIssuePerYear: getValue(`issuanceCurveFutureValues${i}AmuletToIssuePerYear`),
           validatorRewardPercentage: getValue(`issuanceCurveFutureValues${i}ValidatorRewardPercentage`),
    @@ -70,15 +73,22 @@ export function buildAmuletRulesConfigFromChanges(
           featuredAppRewardCap: getValue(`issuanceCurveFutureValues${i}FeaturedAppRewardCap`),
           unfeaturedAppRewardCap: getValue(`issuanceCurveFutureValues${i}UnfeaturedAppRewardCap`),
           optValidatorFaucetCap: getValue(`issuanceCurveFutureValues${i}OptValidatorFaucetCap`),
    +      optDevelopmentFundPercentage:
    +        futureOptDevelopmentFundPercentage === '' ? null : futureOptDevelopmentFundPercentage,
         };
         futureValues.push({ _1: time, _2: config });
       }
     
       const transferPreapprovalFee = getValue('transferPreapprovalFee');
    +  const optDevelopmentFundManager = getValue('optDevelopmentFundManager');
    +  const initialOptDevelopmentFundPercentage = getValue(
    +    'issuanceCurveInitialValueOptDevelopmentFundPercentage'
    +  );
       const amuletConfig: AmuletConfig<'USD'> = {
         tickDuration: { microseconds: getValue('tickDuration') },
         transferPreapprovalFee: transferPreapprovalFee === '' ? null : transferPreapprovalFee,
         featuredAppActivityMarkerAmount: getValue('featuredAppActivityMarkerAmount'),
    +    optDevelopmentFundManager: optDevelopmentFundManager === '' ? null : optDevelopmentFundManager,
     
         transferConfig: {
           createFee: { fee: getValue('transferConfigCreateFee') },
    @@ -103,6 +113,8 @@ export function buildAmuletRulesConfigFromChanges(
             featuredAppRewardCap: getValue('issuanceCurveInitialValueFeaturedAppRewardCap'),
             unfeaturedAppRewardCap: getValue('issuanceCurveInitialValueUnfeaturedAppRewardCap'),
             optValidatorFaucetCap: getValue('issuanceCurveInitialValueOptValidatorFaucetCap'),
    +        optDevelopmentFundPercentage:
    +          initialOptDevelopmentFundPercentage === '' ? null : initialOptDevelopmentFundPercentage,
           },
           futureValues: futureValues,
         },
    diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala
    index 63bcb92fc0..62d283a439 100644
    --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala
    +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala
    @@ -116,6 +116,9 @@ class DsoDelegateBasedAutomationService(
             svTaskContext,
           )
         )
    +    registerTrigger(
    +      new MergeUnclaimedDevelopmentFundCouponsTrigger(config, triggerContext, svTaskContext)
    +    )
       }
     
     }
    @@ -149,5 +152,6 @@ object DsoDelegateBasedAutomationService extends AutomationServiceCompanion {
         aTrigger[AllocateUnallocatedUnclaimedActivityRecordTrigger],
         aTrigger[ExpiredUnallocatedUnclaimedActivityRecordTrigger],
         aTrigger[ExpiredUnclaimedActivityRecordTrigger],
    +    aTrigger[MergeUnclaimedDevelopmentFundCouponsTrigger],
       )
     }
    diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/MergeUnclaimedDevelopmentFundCouponsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/MergeUnclaimedDevelopmentFundCouponsTrigger.scala
    new file mode 100644
    index 0000000000..5a152077e4
    --- /dev/null
    +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/MergeUnclaimedDevelopmentFundCouponsTrigger.scala
    @@ -0,0 +1,115 @@
    +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package org.lfdecentralizedtrust.splice.sv.automation.delegatebased
    +
    +import org.lfdecentralizedtrust.splice.automation.{
    +  PollingParallelTaskExecutionTrigger,
    +  TaskOutcome,
    +  TaskSuccess,
    +  TriggerContext,
    +}
    +import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.UnclaimedDevelopmentFundCoupon
    +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.DsoRules_MergeUnclaimedDevelopmentFundCoupons
    +import org.lfdecentralizedtrust.splice.store.PageLimit
    +import org.lfdecentralizedtrust.splice.util.Contract
    +import org.lfdecentralizedtrust.splice.util.PrettyInstances.*
    +import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
    +import com.digitalasset.canton.tracing.TraceContext
    +import io.opentelemetry.api.trace.Tracer
    +import org.apache.pekko.stream.Materializer
    +import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.AmuletRules_MergeUnclaimedDevelopmentFundCoupons
    +import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority
    +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig
    +
    +import scala.concurrent.{ExecutionContext, Future}
    +import scala.jdk.CollectionConverters.*
    +
    +class MergeUnclaimedDevelopmentFundCouponsTrigger(
    +    svConfig: SvAppBackendConfig,
    +    override protected val context: TriggerContext,
    +    override protected val svTaskContext: SvTaskBasedTrigger.Context,
    +)(implicit
    +    override val ec: ExecutionContext,
    +    mat: Materializer,
    +    tracer: Tracer,
    +) extends PollingParallelTaskExecutionTrigger[MergeUnclaimedDevelopmentFundCouponsTask]
    +    with SvTaskBasedTrigger[MergeUnclaimedDevelopmentFundCouponsTask] {
    +
    +  private val store = svTaskContext.dsoStore
    +
    +  protected def retrieveTasks()(implicit
    +      tc: TraceContext
    +  ): Future[Seq[MergeUnclaimedDevelopmentFundCouponsTask]] = {
    +    val threshold = svConfig.unclaimedDevelopmentFundCouponsThreshold
    +    val limit = PageLimit.tryCreate(2 * threshold)
    +    store.listUnclaimedDevelopmentFundCoupons(limit).map { unclaimedDevelopmentFundCoupons =>
    +      if (unclaimedDevelopmentFundCoupons.length >= 2 * threshold) {
    +        Seq(
    +          MergeUnclaimedDevelopmentFundCouponsTask(
    +            // Merge the `threshold` smallest coupons (by amount) to keep larger coupons stable and
    +            // reduce contention with externally prepared transactions referencing contract-ids.
    +            unclaimedDevelopmentFundCoupons.sortBy(_.payload.amount).take(threshold)
    +          )
    +        )
    +      } else {
    +        Seq()
    +      }
    +    }
    +  }
    +
    +  protected def isStaleTask(
    +      unclaimedDevelopmentFundCouponsTask: MergeUnclaimedDevelopmentFundCouponsTask
    +  )(implicit tc: TraceContext): Future[Boolean] = store.multiDomainAcsStore.containsArchived(
    +    unclaimedDevelopmentFundCouponsTask.contractsToMerge.map(_.contractId)
    +  )
    +
    +  override def completeTaskAsDsoDelegate(
    +      unclaimedDevelopmentFundCouponsTask: MergeUnclaimedDevelopmentFundCouponsTask,
    +      controller: String,
    +  )(implicit tc: TraceContext): Future[TaskOutcome] = {
    +    for {
    +      dsoRules <- store.getDsoRules()
    +      amuletRules <- store.getAmuletRules()
    +      choiceArg = new AmuletRules_MergeUnclaimedDevelopmentFundCoupons(
    +        unclaimedDevelopmentFundCouponsTask.contractsToMerge
    +          .map(_.contractId)
    +          .asJava
    +      )
    +      arg = new DsoRules_MergeUnclaimedDevelopmentFundCoupons(
    +        amuletRules.contractId,
    +        choiceArg,
    +        controller,
    +      )
    +      cmd = dsoRules.exercise(_.exerciseDsoRules_MergeUnclaimedDevelopmentFundCoupons(arg))
    +      res <- for {
    +        outcome <- svTaskContext
    +          .connection(SpliceLedgerConnectionPriority.Low)
    +          .submit(
    +            Seq(store.key.svParty),
    +            Seq(store.key.dsoParty),
    +            cmd,
    +          )
    +          .noDedup
    +          .yieldResult()
    +      } yield Some(outcome)
    +    } yield {
    +      res
    +        .map(cid => {
    +          TaskSuccess(
    +            s"Merged unclaimed development fund coupons into contract ${cid.exerciseResult.result.unclaimedDevelopmentFundCouponCid.contractId}"
    +          )
    +        })
    +        .getOrElse(TaskSuccess(s"Not enough unclaimed development fund coupons to merge"))
    +    }
    +  }
    +}
    +
    +case class MergeUnclaimedDevelopmentFundCouponsTask(
    +    contractsToMerge: Seq[
    +      Contract[UnclaimedDevelopmentFundCoupon.ContractId, UnclaimedDevelopmentFundCoupon]
    +    ]
    +) extends PrettyPrinting {
    +  override def pretty: Pretty[this.type] =
    +    prettyOfClass(param("contractsToMerge", _.contractsToMerge))
    +}
    diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala
    index 2f2c7a1a9f..d5f57e229a 100644
    --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala
    +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala
    @@ -102,6 +102,7 @@ object SvOnboardingConfig {
           initialFeaturedAppActivityMarkerAmount: Option[BigDecimal] = Some(BigDecimal(1.0)),
           voteCooldownTime: Option[NonNegativeFiniteDuration] = None,
           initialRound: Long = 0L,
    +      developmentFundPercentage: Option[BigDecimal] = None,
       ) extends SvOnboardingConfig
     
       case class JoinWithKey(
    @@ -363,6 +364,8 @@ case class SvAppBackendConfig(
         // If true, we check that topology on mediator and sequencer is the same after
         // a migration. This can be a useful assertion but is very slow so should not be enabled on clusters with large topology state.
         validateTopologyAfterMigration: Boolean = false,
    +    // The threshold above which unclaimed development fund coupons will be merged.
    +    unclaimedDevelopmentFundCouponsThreshold: Int = 10,
     ) extends SpliceBackendConfig {
     
       def shouldSkipSynchronizerInitialization =
    diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/sv1/SV1Initializer.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/sv1/SV1Initializer.scala
    index a647d846d3..e122f0a571 100644
    --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/sv1/SV1Initializer.scala
    +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/sv1/SV1Initializer.scala
    @@ -671,22 +671,28 @@ class SV1Initializer(
                         show"This should never happen.\nAmuletRules: $amuletRules"
                     )
                   case None =>
    -                val amuletConfig = defaultAmuletConfig(
    -                  sv1Config.initialTickDuration,
    -                  sv1Config.initialMaxNumInputs,
    -                  synchronizerId,
    -                  sv1Config.initialSynchronizerFeesConfig.extraTrafficPrice.value,
    -                  sv1Config.initialSynchronizerFeesConfig.minTopupAmount.value,
    -                  sv1Config.initialSynchronizerFeesConfig.baseRateBurstAmount.value,
    -                  sv1Config.initialSynchronizerFeesConfig.baseRateBurstWindow,
    -                  sv1Config.initialSynchronizerFeesConfig.readVsWriteScalingFactor.value,
    -                  sv1Config.initialPackageConfig.toPackageConfig,
    -                  sv1Config.initialHoldingFee,
    -                  sv1Config.zeroTransferFees,
    -                  sv1Config.initialTransferPreapprovalFee,
    -                  sv1Config.initialFeaturedAppActivityMarkerAmount,
    -                )
                     for {
    +                  developmentFund <- packageVersionSupport.supportDevelopmentFund(
    +                    Seq(svParty),
    +                    clock.now,
    +                  )
    +                  amuletConfig = defaultAmuletConfig(
    +                    sv1Config.initialTickDuration,
    +                    sv1Config.initialMaxNumInputs,
    +                    synchronizerId,
    +                    sv1Config.initialSynchronizerFeesConfig.extraTrafficPrice.value,
    +                    sv1Config.initialSynchronizerFeesConfig.minTopupAmount.value,
    +                    sv1Config.initialSynchronizerFeesConfig.baseRateBurstAmount.value,
    +                    sv1Config.initialSynchronizerFeesConfig.baseRateBurstWindow,
    +                    sv1Config.initialSynchronizerFeesConfig.readVsWriteScalingFactor.value,
    +                    sv1Config.initialPackageConfig.toPackageConfig,
    +                    sv1Config.initialHoldingFee,
    +                    sv1Config.zeroTransferFees,
    +                    sv1Config.initialTransferPreapprovalFee,
    +                    sv1Config.initialFeaturedAppActivityMarkerAmount,
    +                    developmentFundPercentage =
    +                      if (developmentFund.supported) sv1Config.developmentFundPercentage else None,
    +                  )
                       sv1SynchronizerNodes <- SvUtil.getSV1SynchronizerNodeConfig(
                         cometBftNode,
                         localSynchronizerNode,
    diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala
    index 9ff3f3baf5..bb81e7dd19 100644
    --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala
    +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala
    @@ -6,7 +6,10 @@ package org.lfdecentralizedtrust.splice.sv.store
     import cats.implicits.toTraverseOps
     import com.digitalasset.daml.lf.data.Time.Timestamp
     import org.lfdecentralizedtrust.splice.automation.MultiDomainExpiredContractTrigger.ListExpiredContracts
    -import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.UnclaimedReward
    +import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.{
    +  UnclaimedDevelopmentFundCoupon,
    +  UnclaimedReward,
    +}
     import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.{
       AmuletRules_MiningRound_Archive,
       AppTransferContext,
    @@ -958,6 +961,21 @@ trait SvDsoStore
           .listContracts(splice.amulet.FeaturedAppActivityMarker.COMPANION, PageLimit.tryCreate(limit))
           .map(_.map(_.contract))
     
    +  final def listUnclaimedDevelopmentFundCoupons(
    +      limit: Limit
    +  )(implicit
    +      tc: TraceContext
    +  ): Future[Seq[Contract[
    +    UnclaimedDevelopmentFundCoupon.ContractId,
    +    splice.amulet.UnclaimedDevelopmentFundCoupon,
    +  ]]] =
    +    for {
    +      unclaimedDevelopmentFundCoupon <- multiDomainAcsStore.listContracts(
    +        splice.amulet.UnclaimedDevelopmentFundCoupon.COMPANION,
    +        limit = limit,
    +      )
    +    } yield unclaimedDevelopmentFundCoupon map (_.contract)
    +
       /** Whether there are more than the given number of featured app activity markers. */
       def featuredAppActivityMarkerCountAboveOrEqualTo(threshold: Int)(implicit
           tc: TraceContext
    @@ -1340,6 +1358,13 @@ object SvDsoStore {
                 Some(PartyId.tryFromProtoPrimitive(contract.payload.publisher)),
             )
           },
    +      mkFilter(splice.amulet.UnclaimedDevelopmentFundCoupon.COMPANION)(co =>
    +        co.payload.dso == dso
    +      ) { contract =>
    +        DsoAcsStoreRowData(
    +          contract
    +        )
    +      },
         )
     
         MultiDomainAcsStore.SimpleContractFilter(
    diff --git a/apps/wallet/frontend/src/__tests__/mocks/constants.ts b/apps/wallet/frontend/src/__tests__/mocks/constants.ts
    index 578ca209cf..93365fdc4e 100644
    --- a/apps/wallet/frontend/src/__tests__/mocks/constants.ts
    +++ b/apps/wallet/frontend/src/__tests__/mocks/constants.ts
    @@ -199,6 +199,7 @@ export const amuletRules = {
                     amuletToIssuePerYear: '40000000000.0',
                     validatorRewardCap: '0.2',
                     optValidatorFaucetCap: '2.85',
    +                optDevelopmentFundPercentage: '0.05',
                   },
                   futureValues: [
                     {
    @@ -213,6 +214,7 @@ export const amuletRules = {
                         amuletToIssuePerYear: '20000000000.0',
                         validatorRewardCap: '0.2',
                         optValidatorFaucetCap: '2.85',
    +                    optDevelopmentFundPercentage: '0.05',
                       },
                     },
                     {
    @@ -227,6 +229,7 @@ export const amuletRules = {
                         amuletToIssuePerYear: '10000000000.0',
                         validatorRewardCap: '0.2',
                         optValidatorFaucetCap: '2.85',
    +                    optDevelopmentFundPercentage: '0.05',
                       },
                     },
                     {
    @@ -241,6 +244,7 @@ export const amuletRules = {
                         amuletToIssuePerYear: '5000000000.0',
                         validatorRewardCap: '0.2',
                         optValidatorFaucetCap: '2.85',
    +                    optDevelopmentFundPercentage: '0.05',
                       },
                     },
                     {
    @@ -255,12 +259,14 @@ export const amuletRules = {
                         amuletToIssuePerYear: '2500000000.0',
                         validatorRewardCap: '0.2',
                         optValidatorFaucetCap: '2.85',
    +                    optDevelopmentFundPercentage: '0.05',
                       },
                     },
                   ],
                 },
                 transferPreapprovalFee: null,
                 featuredAppActivityMarkerAmount: null,
    +            optDevelopmentFundManager: null,
               },
               futureValues: [],
             },
    @@ -300,6 +306,7 @@ export const miningRounds = {
                 amuletToIssuePerYear: '40000000000.0',
                 validatorRewardCap: '0.2',
                 optValidatorFaucetCap: '2.85',
    +            optDevelopmentFundPercentage: '0.05',
               },
               opensAt: '2024-08-05T16:30:50.974657Z',
               transferConfigUsd: {
    @@ -369,6 +376,7 @@ export const miningRounds = {
                 amuletToIssuePerYear: '40000000000.0',
                 validatorRewardCap: '0.2',
                 optValidatorFaucetCap: '2.85',
    +            optDevelopmentFundPercentage: '0.05',
               },
               opensAt: '2024-08-05T16:41:21.419411Z',
               transferConfigUsd: {
    @@ -438,6 +446,7 @@ export const miningRounds = {
                 amuletToIssuePerYear: '40000000000.0',
                 validatorRewardCap: '0.2',
                 optValidatorFaucetCap: '2.85',
    +            optDevelopmentFundPercentage: '0.05',
               },
               opensAt: '2024-08-05T16:51:47.336118Z',
               transferConfigUsd: {
    diff --git a/daml/dars.lock b/daml/dars.lock
    index 8664cb7ccd..4a4d755093 100644
    --- a/daml/dars.lock
    +++ b/daml/dars.lock
    @@ -5,6 +5,7 @@ splice-amulet 0.1.11 9824927cdb455f833867b74c01cffcd8cb8cc5edd4d2273cea1329b708e
     splice-amulet 0.1.12 95a88ff9ffd509e097802ecf3bbd58c83a5dff408e439cca4e2105ebd2cd0760
     splice-amulet 0.1.13 6e9fc50fb94e56751b49f09ba2dc84da53a9d7cff08115ebb4f6b7a12d0c990c
     splice-amulet 0.1.14 3ca1343ab26b453d38c8adb70dca5f1ead8440c42b59b68f070786955cbf9ec1
    +splice-amulet 0.1.15 67fac2f853bce8dbf0b9817bb5ba7c59f10e8120b7c808696f7010e5f0c8a791
     splice-amulet 0.1.2 1446ffdf23326cef2de97923df96618eb615792bea36cf1431f03639448f1645
     splice-amulet 0.1.3 0d89016d5a90eb8bced48bbac99e81c57781b3a36094b8d48b8e4389851e19af
     splice-amulet 0.1.4 a36ef8888fb44caae13d96341ce1fabd84fc9e2e7b209bbc3caabb48b6be1668
    @@ -21,6 +22,7 @@ splice-amulet-name-service 0.1.12 557a74491324790b5cf5a379f2481ab1cd2c8b75530858
     splice-amulet-name-service 0.1.13 0e8c7e1cb828336bb4d537f6c4ecd94128b94733eb2ae1f55b5962757d357b4b
     splice-amulet-name-service 0.1.14 6cb1318176e758c256c2e385f87b86c5060e80fb68a72e8ceb08ac5f9045fff2
     splice-amulet-name-service 0.1.15 d4724b90dce9fb08badbb367962d237710b3a603e4f57806a1b0af308cc70fdb
    +splice-amulet-name-service 0.1.16 53468a38bce11b51cd2ed10b9c09301c0b73570b50896d5649c4629de15815a3
     splice-amulet-name-service 0.1.2 711a2974d65e6ebd149704da75f3f71234798687ab895b92f066c865dbdeeabb
     splice-amulet-name-service 0.1.3 beb4b85f3f0cf36dfb93fc917d3ac218ee5d41b6e70604720cb228d85e168ee0
     splice-amulet-name-service 0.1.4 053c7f4c2a77312e7d465a4fa7dc8cb298754ad12c0c987a7c401bd724e65efc
    @@ -29,8 +31,8 @@ splice-amulet-name-service 0.1.6 a208aab2c4a248ab2eff352bd382f8b3bbadc92464123db
     splice-amulet-name-service 0.1.7 ba7806d9b2d593eac74a050161c54ae1325d170bf175cb66a9c1e5e5ffb88c3d
     splice-amulet-name-service 0.1.8 efeb3f9b2b92e55fac4ec2d6164f95407a01477240c7465e576df4e310f54bd3
     splice-amulet-name-service 0.1.9 f1b5915ad45ded616f43f83c735b7ee158b5eb58abe758a721e50eee19b3e531
    -splice-amulet-name-service-test 0.1.18 ccf831c39f1e686f0671bc56b13bdb37f48f9d684ebb84673120df68dde0ad28
    -splice-amulet-test 0.1.17 c89d7f4966236ecf0cc1d9d1ad5c12d3be3b2935e5ccb7feb35de8d889bc0b1b
    +splice-amulet-name-service-test 0.1.19 6542e39b3393da6895548049bed03208db77b274140557c9d9d3be13c77ec885
    +splice-amulet-test 0.1.18 a488e6bd06305296d918e1485abb8103e4984eacc09d0d5f558cac44e4f19e4f
     splice-api-featured-app-v1 1.0.0 7804375fe5e4c6d5afe067bd314c42fe0b7d005a1300019c73154dd939da4dda
     splice-api-token-allocation-instruction-v1 1.0.0 275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520
     splice-api-token-allocation-request-v1 1.0.0 6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193
    @@ -55,6 +57,7 @@ splice-dso-governance 0.1.18 136484875714fcf24c24858e1d573ac756524eeb607c26999fc
     splice-dso-governance 0.1.19 759d1cf002fc1225ac43a55d73f0058becce3e62cfb5485c197f2b69ed8d9d98
     splice-dso-governance 0.1.2 4206e127be8b111ac84bd7f98bd9dbf03ed489f1642b46ab31a46ee6d688e7e8
     splice-dso-governance 0.1.20 996a3b619d6b65ca7812881978c44c650cac119de78f5317d1f317658943001c
    +splice-dso-governance 0.1.21 2d306cfe8cdb3daf2d21f84dfecc3e2f26a41504e58fe25cb7fe5cc65683d11f
     splice-dso-governance 0.1.3 b0ae3cc03e418790305a3c15f761fe495572de5827f8d322fb8b96996b783c13
     splice-dso-governance 0.1.4 dc24fd18b4d151cd1e0ff6bfb7438bafb2f50fe076d0f16f50565e60b153a0be
     splice-dso-governance 0.1.5 9e3ca1d22ad495dfabf3d61acae3dc1a7718f527f02092280b58cf69edfdc84c
    @@ -62,8 +65,8 @@ splice-dso-governance 0.1.6 4e7653cfbf7ca249de4507aca9cd3b91060e5489042a522c589d
     splice-dso-governance 0.1.7 d406eba1132d464605f4dae3edf8cf5ecbbb34bd8edef0e047e7e526d328718c
     splice-dso-governance 0.1.8 1790a114f83d5f290261fae1e7e46fba75a861a3dd603c6b4ef6b67b49053948
     splice-dso-governance 0.1.9 9ee83bfd872f91e659b8a8439c5b4eaf240bcf6f19698f884d7d7993ab48c401
    -splice-dso-governance-test 0.1.24 0b70f6e759a482e17b01c16c05791b995d27ae24f72ac54ab47e9e37f5c14acc
    -splice-token-standard-test 1.0.8 17b088db69a7ab44014bb23d24c0a348c817457b762c991895e1bdc0e21bcad0
    +splice-dso-governance-test 0.1.25 a02cba80f1d2035184558614cc2030e7c714e6e549fbbb1e8180a5151ddc3913
    +splice-token-standard-test 1.0.9 7010184aafc43bd9db506e02e660593d8bcdd48972b136684b70284f036363db
     splice-token-test-dummy-holding 0.0.1 1cd171c6c42ab46dc9cf12d80c6111369e00cea5cdf054924b4f26ce94b1ef5b
     splice-token-test-dummy-holding 0.0.2 4f40fb033ef3db89623642c1b494e846097fa32af138b3864a63aa15937a323d
     splice-token-test-trading-app 1.0.0 e5c9847d5a88d3b8d65436f01765fc5ba142cc58529692e2dacdd865d9939f71
    @@ -72,20 +75,22 @@ splice-util 0.1.1 00bf3632ca479d56e536096ca23bbc75d15084088ab5d12e4b31d6547d1df3
     splice-util 0.1.2 3eb8f9ff160b782e0bf7ef0351072cfb6f186f086e082b35d9f7a0317e163372
     splice-util 0.1.3 a0538353ed6b7be1a596d94fe7c2acb4a89ea9abdc6cff3388b0eebe0452bafb
     splice-util 0.1.4 b7356fbb2cf8a3b22194d8c743c3c216d9c7527b257c8c38b257eb22942be358
    +splice-util 0.1.5 5a58024e2cc488ca9e0c952ec7ef41da3a1ed0a78ba23bacd819e5b30afb5546
     splice-util-featured-app-proxies 1.0.0 48e0c4fe4ea05e3b740404ebe37004ddd741efbdcd665c1c3199a5d6d9d944d7
     splice-util-featured-app-proxies 1.1.0 81dd5a9e5c02d0de03208522a895fb85eeb12fbea4aca7c4ad0ad106f3b0bfce
     splice-util-featured-app-proxies 1.2.0 653c48879064332d34af5008bdfd8e349493460e67e62b85e8e7e3392831c842
     splice-util-featured-app-proxies 1.2.1 06bab917848ef275317c2539b75c23b94e03ceb55b4a1346936f7832084cd7a6
    -splice-util-featured-app-proxies-test 1.0.6 bd75928121fdf25a9b1178950650bf7dd8d89d14b88a3a8a3545d3a8f97029a6
    +splice-util-featured-app-proxies-test 1.0.7 f78f117532321b952ac8b6dc4a9c83659369d537be52d3f9a6950a87f12d8f43
     splice-util-token-standard-wallet 1.0.0 1da198cb7968fa478cfa12aba9fdf128a63a8af6ab284ea6be238cf92a3733ac
    -splice-util-token-standard-wallet-test 1.0.1 876136c37fe37e8fde126631d273ddcdee38a895dbb5ca1fed630d182ff74fc6
    +splice-util-token-standard-wallet-test 1.0.2 b2d3452df2c64bda9ff0ecf11fa07ed61344415328ed08cb807938295232b134
     splice-validator-lifecycle 0.1.0 cef96fac957362f1fc097120bd13686cac7f84fbc8053afa994a1f9214d9570c
     splice-validator-lifecycle 0.1.1 1ddf05c96002914593c929848b786f34c753fb0be07717d1786be177a564aada
     splice-validator-lifecycle 0.1.2 57e2f15f9755db1f00e51c52c319294264a21ad71c6bc1e7cd70db4b164c0aaa
     splice-validator-lifecycle 0.1.3 33cbc8ef9f2937b581f33e0a19cdafc632084bd6e2f34d73174774fc519f8930
     splice-validator-lifecycle 0.1.4 9333354aaaf7378ff228a4c8e51fadec8f4b82308f3343b1c4b68ae13dafa849
     splice-validator-lifecycle 0.1.5 455dd4533c2dd0131fb349c93d9d35f3670901d13efadb0aa9b975d35b41dbb2
    -splice-validator-lifecycle-test 0.1.5 cf44c0dc8d3cd06658f2f62d779731f8b33117d1064d949f763fd438169e64c3
    +splice-validator-lifecycle 0.1.6 a717b2b9c23f630eb1ac907445fa5f5c9cce87fca28374b6165bc510724e07e7
    +splice-validator-lifecycle-test 0.1.6 f7b1298c13fe7c4c5044d696db80b3374acb8785e089cd82906d21f26adb2ca5
     splice-wallet 0.1.0 5f384fba007e8b93c8ff1ac151cdc2d035ae1e0506f5e5770de469048713024c
     splice-wallet 0.1.1 e276113de450c2bb4bf8cf2d2991dd0b397fe7fe8103fe1b5377b8ab6622edec
     splice-wallet 0.1.10 9824ffa414d5803d17efafd75c69c1dd53591ac5197d07048b24dbac1720a462
    @@ -93,6 +98,7 @@ splice-wallet 0.1.11 991842eee48ec3caa3a649e8f47e3544dd7b688ce4b363aa934a83db7da
     splice-wallet 0.1.12 b30bb727552cf6b624dbc9a5ff95f6c158e0a654e2e9c5c27bcfe3f5d0f9ada2
     splice-wallet 0.1.13 eb6e01efacc3397e23c6be8b9be7db4bf37672211974d69e24b48980e2f98b7e
     splice-wallet 0.1.14 690c1d47bac06db419db344d59a7a30c53fa3f5d961943fe1782cfc6c78794d8
    +splice-wallet 0.1.15 fd57252dda29e3ce90028114c91b521cb661df5a9d6e87c41a9e91518215fa5b
     splice-wallet 0.1.2 c162e08a4ec0428bfa870b6d9040989e575c74199c3a80558c62e03196dd5146
     splice-wallet 0.1.3 2c35bb4f5084ea66db59717d21750bfd64c43147ef5fd5166615092d592a6917
     splice-wallet 0.1.4 141dad2d33b6410b8e1c35a0c4f8f76cb691e4d9a4410ce89f33f373855317e1
    @@ -108,6 +114,7 @@ splice-wallet-payments 0.1.11 7266d861727757f3482857a77f25f4d647d8925b469e46938a
     splice-wallet-payments 0.1.12 88516902a9f045d3fd3835c8f5c8c6bfe4b44d83fae11369241f1883bb5b3ab4
     splice-wallet-payments 0.1.13 0b9250642d3864e6bbea553264dcac0d286104f24efad2fbaf4645520bcb4053
     splice-wallet-payments 0.1.14 45b29d6e05b5352c39edde850c66b4535c682b9991b06eec312176b1a48ecab5
    +splice-wallet-payments 0.1.15 f80fae7a9de9431854372a66c3ca78675f77b2f54ede65abdc1b1abdec707d21
     splice-wallet-payments 0.1.2 775f5eb9c0249509adda5eb3ea4ee31bb953601168c18880df6f2ff09ec4298a
     splice-wallet-payments 0.1.3 b953b3729c81a55e598a364be7d0c0574750df3de12a7a1b53a300f217cb5c5c
     splice-wallet-payments 0.1.4 12177f54873c1094ea169874ad0d7838383fd137f302d16356e93f28dfbc0fcc
    @@ -116,7 +123,7 @@ splice-wallet-payments 0.1.6 6124379528eeb6fa17ecdab15577c29abb33d0c0d34dc5f2680
     splice-wallet-payments 0.1.7 4e3e0d9cdadf80f4bf8f3cd3660d5287c084c9a29f23c901aabce597d72fd467
     splice-wallet-payments 0.1.8 e48ea337ee3335c8bb3206a2501ce947ac1a7bdb1825cee8f28bad64f5a7bc4b
     splice-wallet-payments 0.1.9 7f4e081ad96f2ccded0c053b0cf5ddddae1139dfc3bb89cefcf77ea70f2cecb7
    -splice-wallet-test 0.1.17 502dece51e746b84af6325286f23cf9a591a58ddec3c875ce415ffd5ff6073ec
    +splice-wallet-test 0.1.18 f658717551440b5c05502aee61b834020c4490aef2e4edce118c45313d386f21
     splitwell 0.1.0 075c76de553ab88383a7c69de134afa82aacfdf8ea8fcfe8852c4b199c3b2669
     splitwell 0.1.1 ccb1a0215053062202052e1a052f9214da3fdae5253a6d43e2e155ff4f57fe75
     splitwell 0.1.10 d42676a366f7ca7a2409974dd3054aa4d83ab29baa3b2086ad021407b0a1a295
    @@ -124,6 +131,7 @@ splitwell 0.1.11 03b487fa26a8ef67df0876fb337904624c3fac27f11b7ad2d131a4eab26ee1b
     splitwell 0.1.12 cc047977ee8da70e858f203a14c3fd302c6aaed27be42383e61a026854d76112
     splitwell 0.1.13 c2cf7b5fb3c615cdd2c8e14af42f1ca5fe4df8647cb656c7d02a72420152c3dd
     splitwell 0.1.14 bf2ec3fec9bcb58ed5e2ff63072a1e4994d0415ea7a0275942be282906a42021
    +splitwell 0.1.15 2f3d8a50f57e66af450c36556a09d04c1d9117b699720118b7bd302556805499
     splitwell 0.1.2 778edd2c228c6b68198d4d033885b2d0dae7daaee55d7df3edd9dfdf1f10fbd0
     splitwell 0.1.3 7cde068cde689584f86a2499689d5cb165264d96496721e24ac6fb909f770a58
     splitwell 0.1.4 85557b86cd4f330f093915db1ea26eac5092de6b5ddae0690146f6059c89419b
    @@ -132,4 +140,4 @@ splitwell 0.1.6 872da0dd7986fd768930f85d6a7310a94a0ef924e7fbb7bb7a4e149f2b5feb74
     splitwell 0.1.7 841d1c9c86b5c8f3a39059459ecd8febedf7703e18f117300bb0ebf4423db096
     splitwell 0.1.8 63b8153a08ceb4bf40d807acc5712372c3eac548c266be4d5e92470b4f655515
     splitwell 0.1.9 b6267905698d2798b9ef171e27d49fb88e052ec0ec0e0675a3a1b275c7d037d4
    -splitwell-test 0.1.17 3f6daa25da9caec80a986c0f60eecbd8d62c248fba325f4422cbe39452651485
    \ No newline at end of file
    +splitwell-test 0.1.18 022ee9f4acf3c29af6abcc21ec8d5e3acac2dadfec123adfc97932b1f13348a6
    \ No newline at end of file
    diff --git a/daml/dars/splice-amulet-0.1.15.dar b/daml/dars/splice-amulet-0.1.15.dar
    new file mode 100644
    index 0000000000..924f3429f9
    Binary files /dev/null and b/daml/dars/splice-amulet-0.1.15.dar differ
    diff --git a/daml/dars/splice-amulet-name-service-0.1.16.dar b/daml/dars/splice-amulet-name-service-0.1.16.dar
    new file mode 100644
    index 0000000000..ae1ee069c4
    Binary files /dev/null and b/daml/dars/splice-amulet-name-service-0.1.16.dar differ
    diff --git a/daml/dars/splice-dso-governance-0.1.21.dar b/daml/dars/splice-dso-governance-0.1.21.dar
    new file mode 100644
    index 0000000000..7b6698cbbd
    Binary files /dev/null and b/daml/dars/splice-dso-governance-0.1.21.dar differ
    diff --git a/daml/dars/splice-util-0.1.5.dar b/daml/dars/splice-util-0.1.5.dar
    new file mode 100644
    index 0000000000..4500ebd9c6
    Binary files /dev/null and b/daml/dars/splice-util-0.1.5.dar differ
    diff --git a/daml/dars/splice-validator-lifecycle-0.1.6.dar b/daml/dars/splice-validator-lifecycle-0.1.6.dar
    new file mode 100644
    index 0000000000..1be4d1eabc
    Binary files /dev/null and b/daml/dars/splice-validator-lifecycle-0.1.6.dar differ
    diff --git a/daml/dars/splice-wallet-0.1.15.dar b/daml/dars/splice-wallet-0.1.15.dar
    new file mode 100644
    index 0000000000..de71426413
    Binary files /dev/null and b/daml/dars/splice-wallet-0.1.15.dar differ
    diff --git a/daml/dars/splice-wallet-payments-0.1.15.dar b/daml/dars/splice-wallet-payments-0.1.15.dar
    new file mode 100644
    index 0000000000..05036f01e3
    Binary files /dev/null and b/daml/dars/splice-wallet-payments-0.1.15.dar differ
    diff --git a/daml/dars/splitwell-0.1.15.dar b/daml/dars/splitwell-0.1.15.dar
    new file mode 100644
    index 0000000000..3f144e6116
    Binary files /dev/null and b/daml/dars/splitwell-0.1.15.dar differ
    diff --git a/daml/splice-amulet-name-service-test/daml.yaml b/daml/splice-amulet-name-service-test/daml.yaml
    index 6f34b4eade..962a21b446 100644
    --- a/daml/splice-amulet-name-service-test/daml.yaml
    +++ b/daml/splice-amulet-name-service-test/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-amulet-name-service-test
     source: daml
    -version: 0.1.18
    +version: 0.1.19
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-amulet-name-service/daml.yaml b/daml/splice-amulet-name-service/daml.yaml
    index 83452c24b4..7259ecb172 100644
    --- a/daml/splice-amulet-name-service/daml.yaml
    +++ b/daml/splice-amulet-name-service/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-amulet-name-service
     source: daml
    -version: 0.1.15
    +version: 0.1.16
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-amulet-test/daml.yaml b/daml/splice-amulet-test/daml.yaml
    index af4110d5ac..e41267fa5e 100644
    --- a/daml/splice-amulet-test/daml.yaml
    +++ b/daml/splice-amulet-test/daml.yaml
    @@ -6,7 +6,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-amulet-test
     source: daml
    -version: 0.1.17
    +version: 0.1.18
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml
    index 50d504a95b..d0fb9adb9c 100644
    --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml
    @@ -3,6 +3,7 @@
     
     module Splice.Scripts.TestAmuletRulesTransfer where
     
    +import DA.Action (replicateA_)
     import DA.Assert
     import DA.Foldable (forA_)
     import DA.List
    @@ -110,6 +111,7 @@ testUsageFees = do
         inputUnclaimedActivityRecordAmount = Some 0.0
         inputValidatorFaucetAmount = Some 0.0
         inputSvRewardAmount = 0.0
    +    inputDevelopmentFundAmount = Some 0.0
         inputAmuletAmount = 100.0
         balanceChanges = Map.empty
         holdingFees = 0.0 -- no holding fees charged on transfer
    @@ -574,5 +576,68 @@ testUnclaimedActivityRecordTransferInput = do
       (_, openRound) <- getLatestActiveOpenRound app
       let createFee = openRound.transferConfigUsd.createFee.fee * openRound.amuletPrice
       transferResult.summary.senderChangeAmount === amountToMint - createFee
    +  transferResult.summary.inputUnclaimedActivityRecordAmount === Some amountToMint
    +
    +  pure ()
    +
    +testInputDevelopmentFundCoupon : Script ()
    +testInputDevelopmentFundCoupon = do
    +  defaultAppWithUsers@DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
    +  baseConfig <- getAmuletConfig app
    +  -- Replace the default AmuletConfig with a 0.05% allocation for the development fund.
    +  -- This configuration applies to the next open round.
    +  replaceDevelopmentFundConfig app baseConfig (Some alice.primaryParty) (Some 0.05)
    +
    +  -- Run 3 issuances for the already open rounds without the development fund configuration.
    +  -- No development fund coupons are minted.
    +  replicateA_ 3 $ runNextIssuance app
    +  -- Mint one development fund coupon per issuance (two total).
    +  runNextIssuance app
    +  runNextIssuance app
    +  context <- getPaymentTransferContext app alice
    +  [(cid1, coupon1), (cid2, coupon2)] <- query @UnclaimedDevelopmentFundCoupon app.dso
    +  let totalAmount = coupon1.amount + coupon2.amount
    +
    +  -- Allocate a DevelopmentFundCoupon
    +  now <- getTime
    +  let
    +    expiresAt = addRelTime now (days 2)
    +    reason = "Bob fixed issue XXX"
    +  AmuletRules_AllocateDevelopmentFundCouponResult
    +    { developmentFundCouponCid, optUnclaimedDevelopmentFundCouponCid = None } <-
    +      allocateDevelopmentFundCoupon app alice.primaryParty bob.primaryParty totalAmount expiresAt reason [cid1, cid2]
    +
    +  -- Unhappy - expiresAt has been reached
    +  setTime $ addRelTime expiresAt (minutes 1)
    +  submitMultiMustFail [bob.primaryParty] [app.dso] $
    +    exerciseCmd context.amuletRules AmuletRules_Transfer with
    +      transfer = Transfer with
    +        sender = bob.primaryParty
    +        provider = bob.primaryParty
    +        inputs = [InputDevelopmentFundCoupon developmentFundCouponCid]
    +        outputs = []
    +        beneficiaries = None
    +      context = context.context
    +      expectedDso = Some app.dso
    +  setTime now
    +
    +  -- Happy
    +  transferResult <-
    +    checkTransferMetadata app TxKind_MergeSplit bob.primaryParty $
    +    checkBalanceChanges defaultAppWithUsers $
    +      submitMulti [bob.primaryParty] [app.dso] $
    +        exerciseCmd context.amuletRules AmuletRules_Transfer with
    +          transfer = Transfer with
    +            sender = bob.primaryParty
    +            provider = bob.primaryParty
    +            inputs = [InputDevelopmentFundCoupon developmentFundCouponCid]
    +            outputs = []
    +            beneficiaries = None
    +          context = context.context
    +          expectedDso = Some app.dso
    +  (_, openRound) <- getLatestActiveOpenRound app
    +  let createFee = openRound.transferConfigUsd.createFee.fee * openRound.amuletPrice
    +  transferResult.summary.senderChangeAmount === totalAmount - createFee
    +  transferResult.summary.inputDevelopmentFundAmount === Some totalAmount
     
       pure ()
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml
    index 05ba573799..ccf1084c82 100644
    --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml
    @@ -158,6 +158,7 @@ test_designExample= do
         inputUnclaimedActivityRecordAmount = Some 0.0
         inputValidatorFaucetAmount = Some 0.0
         inputSvRewardAmount = 0.0
    +    inputDevelopmentFundAmount = Some 0.0
         inputAmuletAmount = refreshAmuletAmount + lockedAmuletAmount
         balanceChanges = Map.empty
         holdingFees
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestDevelopmentFundCoupon.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestDevelopmentFundCoupon.daml
    new file mode 100644
    index 0000000000..9a66f47446
    --- /dev/null
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestDevelopmentFundCoupon.daml
    @@ -0,0 +1,245 @@
    +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
    +-- SPDX-License-Identifier: Apache-2.0
    +
    +module Splice.Scripts.TestDevelopmentFundCoupon where
    +
    +import DA.Action (replicateA_)
    +import DA.Assert
    +import DA.Time
    +import Daml.Script
    +
    +import Splice.Amulet
    +import Splice.AmuletRules
    +import Splice.Scripts.Util
    +
    +testAllocateDevelopmentFundCoupon : Script ()
    +testAllocateDevelopmentFundCoupon = do
    +  DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
    +  -- Replace the default AmuletConfig with a 0.05% allocation for the development fund.
    +  -- No Fund manager configured.
    +  -- This configuration applies to the next open round.
    +  baseConfig <- getAmuletConfig app
    +  replaceDevelopmentFundConfig app baseConfig None (Some 0.05)
    +
    +  -- Run 3 issuances for the already open rounds without the development fund configuration.
    +  -- No development fund coupons are minted.
    +  replicateA_ 3 $ runNextIssuance app
    +  -- Mint one development fund coupon per issuance (two total).
    +  runNextIssuance app
    +  runNextIssuance app
    +  now <- getTime
    +  [(cid1, coupon1), (cid2, coupon2)] <- query @UnclaimedDevelopmentFundCoupon app.dso
    +  let
    +    totalAmount = coupon1.amount + coupon2.amount
    +    unclaimedDevelopmentFundCouponCids = [cid1, cid2]
    +    expiresAt = addRelTime now (days 2)
    +    reason = "Bob fixed issue XXX"
    +    firstAllocationAmount = 10.0
    +    secondAllocationAmount = totalAmount - firstAllocationAmount
    +
    +  -- Unhappy - DevelopmentFundCoupon cannot be allocated without a Development Fund manager configured
    +  allocateDevelopmentFundCouponMustFail app alice.primaryParty bob.primaryParty firstAllocationAmount
    +    expiresAt reason unclaimedDevelopmentFundCouponCids
    +
    +  -- Set 5% and Alice as the fund manager
    +  replaceDevelopmentFundConfig app baseConfig (Some alice.primaryParty) (Some 0.05)
    +
    +  -- Unhappy - expiresAt is in the past
    +  let invalidExpiresAt = addRelTime now (minutes (-1))
    +  allocateDevelopmentFundCouponMustFail app alice.primaryParty bob.primaryParty firstAllocationAmount
    +    invalidExpiresAt reason unclaimedDevelopmentFundCouponCids
    +
    +  -- Unhappy - controller must be the configured Development Fund manager (alice)
    +  allocateDevelopmentFundCouponMustFail app bob.primaryParty bob.primaryParty firstAllocationAmount
    +    expiresAt reason unclaimedDevelopmentFundCouponCids
    +
    +  -- Unhappy - insufficient amount to cover the requested allocation
    +  allocateDevelopmentFundCouponMustFail app alice.primaryParty bob.primaryParty (totalAmount + 1.0)
    +    expiresAt reason unclaimedDevelopmentFundCouponCids
    +
    +  -- Happy - Development Fund coupon allocated with UnclaimedDevelopmentFundCoupon leftover
    +  AmuletRules_AllocateDevelopmentFundCouponResult
    +    { developmentFundCouponCid, optUnclaimedDevelopmentFundCouponCid = Some leftOverUnclaimedDevelopmentFundCouponCid } <-
    +    allocateDevelopmentFundCoupon app alice.primaryParty bob.primaryParty firstAllocationAmount
    +      expiresAt reason unclaimedDevelopmentFundCouponCids
    +  Some developmentFundCoupon <- queryContractId bob.primaryParty developmentFundCouponCid
    +  Some leftOverUnclaimedDevelopmentFundCoupon <- queryContractId app.dso leftOverUnclaimedDevelopmentFundCouponCid
    +  developmentFundCoupon === DevelopmentFundCoupon with
    +    dso = app.dso
    +    beneficiary = bob.primaryParty
    +    fundManager = alice.primaryParty
    +    amount = firstAllocationAmount
    +    expiresAt
    +    reason
    +  leftOverUnclaimedDevelopmentFundCoupon === UnclaimedDevelopmentFundCoupon with
    +    dso = app.dso
    +    amount = secondAllocationAmount
    +
    +  -- Happy - Development Fund coupon allocated with no UnclaimedDevelopmentFundCoupon leftover
    +  AmuletRules_AllocateDevelopmentFundCouponResult { developmentFundCouponCid, optUnclaimedDevelopmentFundCouponCid = None } <-
    +    allocateDevelopmentFundCoupon app alice.primaryParty bob.primaryParty secondAllocationAmount
    +      expiresAt reason [leftOverUnclaimedDevelopmentFundCouponCid]
    +  Some developmentFundCoupon <- queryContractId bob.primaryParty developmentFundCouponCid
    +  developmentFundCoupon === DevelopmentFundCoupon with
    +    dso = app.dso
    +    beneficiary = bob.primaryParty
    +    fundManager = alice.primaryParty
    +    amount = secondAllocationAmount
    +    expiresAt
    +    reason
    +
    +  pure ()
    +
    +testWithdrawalOfDevelopmentFundCoupon : Script ()
    +testWithdrawalOfDevelopmentFundCoupon = do
    +  DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
    +  now <- getTime
    +  let
    +    amount = 42.0
    +    expiresAt = addRelTime now (days 1)
    +    fundManager = alice.primaryParty
    +    beneficiary = bob.primaryParty
    +    reason = "Some reason"
    +
    +  developmentFundCouponCid <- createDevelopmentFundCoupon app fundManager beneficiary amount expiresAt reason
    +
    +  -- Unhappy - expiresAt has been reached
    +  setTime $ addRelTime expiresAt (minutes 1)
    +  withdrawDevelopmentFundCouponMustFail fundManager reason developmentFundCouponCid
    +
    +  setTime now
    +
    +  -- Unhappy - invalid controller
    +  withdrawDevelopmentFundCouponMustFail app.dso reason developmentFundCouponCid
    +  withdrawDevelopmentFundCouponMustFail beneficiary reason developmentFundCouponCid
    +
    +  -- Happy
    +  unclaimedDevelopmentFundCouponCid <- withdrawDevelopmentFundCoupon fundManager reason developmentFundCouponCid
    +  Some unclaimedDevelopmentFundCoupon <- queryContractId app.dso unclaimedDevelopmentFundCouponCid
    +  unclaimedDevelopmentFundCoupon === UnclaimedDevelopmentFundCoupon with
    +    dso = app.dso
    +    amount
    +
    +  pure ()
    +
    +testRejectionOfDevelopmentFundCoupon : Script ()
    +testRejectionOfDevelopmentFundCoupon = do
    +  DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
    +  now <- getTime
    +  let
    +    amount = 42.0
    +    expiresAt = addRelTime now (days 1)
    +    fundManager = alice.primaryParty
    +    beneficiary = bob.primaryParty
    +    reason = "Some reason"
    +
    +  developmentFundCouponCid <- createDevelopmentFundCoupon app fundManager beneficiary amount expiresAt reason
    +
    +  -- Unhappy - expiresAt has been reached
    +  setTime $ addRelTime expiresAt (minutes 1)
    +  rejectDevelopmentFundCouponMustFail fundManager reason developmentFundCouponCid
    +
    +  setTime now
    +
    +  -- Unhappy - invalid controller
    +  rejectDevelopmentFundCouponMustFail app.dso reason developmentFundCouponCid
    +  rejectDevelopmentFundCouponMustFail fundManager reason developmentFundCouponCid
    +
    +  -- Happy
    +  unclaimedDevelopmentFundCouponCid <- rejectDevelopmentFundCoupon beneficiary reason developmentFundCouponCid
    +  Some unclaimedDevelopmentFundCoupon <- queryContractId app.dso unclaimedDevelopmentFundCouponCid
    +  unclaimedDevelopmentFundCoupon === UnclaimedDevelopmentFundCoupon with
    +    dso = app.dso
    +    amount
    +
    +  pure ()
    +
    +testExpiryOfDevelopmentFundCoupon : Script ()
    +testExpiryOfDevelopmentFundCoupon = do
    +  DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
    +  now <- getTime
    +  let
    +    amount = 42.0
    +    expiresAt = addRelTime now (days 1)
    +    reason = "Bob fixed issue XXX"
    +    fundManager = alice.primaryParty
    +    beneficiary = bob.primaryParty
    +
    +  developmentFundCouponCid <- createDevelopmentFundCoupon app fundManager beneficiary amount expiresAt reason
    +
    +  -- Unhappy - expiresAt has not been reached
    +  expiresDevelopmentFundCouponMustFail app.dso developmentFundCouponCid
    +
    +  setTime $ addRelTime expiresAt (minutes 1)
    +
    +  -- -- Unhappy - invalid controller
    +  expiresDevelopmentFundCouponMustFail fundManager developmentFundCouponCid
    +  expiresDevelopmentFundCouponMustFail beneficiary developmentFundCouponCid
    +
    +  -- Happy
    +  unclaimedDevelopmentFundCouponCid <- expiresDevelopmentFundCoupon app.dso developmentFundCouponCid
    +  Some unclaimedDevelopmentFundCoupon <- queryContractId app.dso unclaimedDevelopmentFundCouponCid
    +  unclaimedDevelopmentFundCoupon === UnclaimedDevelopmentFundCoupon with
    +    dso = app.dso
    +    amount
    +
    +  pure ()
    +
    +
    +-- Helpers
    +----------
    +
    +allocateDevelopmentFundCouponMustFail
    +  : AmuletApp -> Party -> Party -> Decimal -> Time -> Text -> [ContractId UnclaimedDevelopmentFundCoupon]
    +  -> Script ()
    +allocateDevelopmentFundCouponMustFail app fundManager beneficiary amount expiresAt reason unclaimedDevelopmentFundCouponCids = do
    +  [(amuletRulesCid, _)] <- query @AmuletRules app.dso
    +  submitMultiMustFail [fundManager] [app.dso] do
    +    exerciseCmd amuletRulesCid AmuletRules_AllocateDevelopmentFundCoupon with
    +      unclaimedDevelopmentFundCouponCids
    +      beneficiary
    +      amount
    +      expiresAt
    +      reason
    +      fundManager
    +
    +createDevelopmentFundCoupon : AmuletApp -> Party -> Party -> Decimal -> Time -> Text -> Script (ContractId DevelopmentFundCoupon)
    +createDevelopmentFundCoupon app fundManager beneficiary amount expiresAt reason =
    +  submit app.dso do
    +    createCmd DevelopmentFundCoupon with
    +      dso = app.dso
    +      beneficiary
    +      fundManager
    +      amount
    +      expiresAt
    +      reason
    +
    +rejectDevelopmentFundCoupon : Party -> Text -> ContractId DevelopmentFundCoupon -> Script (ContractId UnclaimedDevelopmentFundCoupon)
    +rejectDevelopmentFundCoupon fundManager reason developmentFundCouponCid =
    +  (.unclaimedDevelopmentFundCouponCid) <$> submit fundManager do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_Reject with reason
    +
    +rejectDevelopmentFundCouponMustFail : Party -> Text -> ContractId DevelopmentFundCoupon -> Script ()
    +rejectDevelopmentFundCouponMustFail actor reason developmentFundCouponCid =
    +  submitMustFail actor do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_Reject with reason
    +
    +withdrawDevelopmentFundCoupon : Party -> Text -> ContractId DevelopmentFundCoupon -> Script (ContractId UnclaimedDevelopmentFundCoupon)
    +withdrawDevelopmentFundCoupon fundManager reason developmentFundCouponCid =
    +  (.unclaimedDevelopmentFundCouponCid) <$> submit fundManager do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_Withdraw with reason
    +
    +withdrawDevelopmentFundCouponMustFail : Party -> Text -> ContractId DevelopmentFundCoupon -> Script ()
    +withdrawDevelopmentFundCouponMustFail actor reason developmentFundCouponCid =
    +  submitMustFail actor do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_Withdraw with reason
    +
    +expiresDevelopmentFundCoupon : Party -> ContractId DevelopmentFundCoupon -> Script (ContractId UnclaimedDevelopmentFundCoupon)
    +expiresDevelopmentFundCoupon dso developmentFundCouponCid =
    +  (.unclaimedDevelopmentFundCouponCid) <$> submit dso do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_DsoExpire
    +
    +expiresDevelopmentFundCouponMustFail : Party -> ContractId DevelopmentFundCoupon -> Script ()
    +expiresDevelopmentFundCouponMustFail actor developmentFundCouponCid =
    +  submitMustFail actor do
    +    exerciseCmd developmentFundCouponCid DevelopmentFundCoupon_DsoExpire
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml
    index f39dae024a..9d25a4e883 100644
    --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml
    @@ -33,6 +33,7 @@ scaleAmuletConfig amuletPrice config = AmuletConfig with
       packageConfig = config.packageConfig
       transferPreapprovalFee = fmap (/ amuletPrice) config.transferPreapprovalFee
       featuredAppActivityMarkerAmount = fmap (/ amuletPrice) config.featuredAppActivityMarkerAmount
    +  optDevelopmentFundManager = config.optDevelopmentFundManager
     
     test : Script ()
     test = script do
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml
    index e32b7b3edb..f657e8b126 100644
    --- a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml
    @@ -21,6 +21,7 @@ tickDuration = defaultAmuletConfig.tickDuration
     amuletPrice : Decimal
     amuletPrice = 0.005
     
    +
     -- Issuance curve retrieval
     ---------------------------
     
    @@ -66,6 +67,16 @@ expectedParameters_E1_0_0p5 = IssuingRoundParameters with
       unclaimedValidatorRewards = 7551.7503805175
       unclaimedAppRewards = 68395.2511415525
       unclaimedSvRewards = 0.0000000001
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E1_0_0p5_DevFund5 : IssuingRoundParameters
    +expectedParameters_E1_0_0p5_DevFund5 =
    +  expectedParameters_E1_0_0p5 with
    +    issuancePerSvRewardCoupon = 5783.8660578387 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation)
    +    unclaimedValidatorRewards = 5649.1628614916 -- decreased by ~25 % (validator tranche near cap: small pool cut sharply reduces unclaimed remainder)
    +    unclaimedAppRewards = 62687.4885844749 -- decreased by ~8.3 % (app tranche less constrained; closer to linear scaling)
    +    unclaimedSvRewards = 0.0 -- decreased ~5 % (below rounding threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 38051.7503805175
     
     expectedParameters_E1_0p5_1p5 : IssuingRoundParameters
     expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with
    @@ -77,6 +88,16 @@ expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with
       unclaimedValidatorRewards = 15162.1004566210
       unclaimedAppRewards = 106447.0015220700
       unclaimedSvRewards = 0.000000004
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E1_0p5_1p5_DevFund5 : IssuingRoundParameters
    +expectedParameters_E1_0p5_1p5_DevFund5 =
    +  expectedParameters_E1_0p5_1p5 with
    +    issuancePerSvRewardCoupon = 1735.1598173516 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation)
    +    unclaimedValidatorRewards = 12878.9954337899 -- decreased by ~15 % (validator tranche near cap: small pool cut sharply reduces unclaimed remainder)
    +    unclaimedAppRewards = 98836.6514459665 -- decreased by ~10 % (app tranche less constrained; closer to linear scaling)
    +    unclaimedSvRewards = 0.0 -- decreased ~5 % (below rounding threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 19025.8751902588
     
     expectedParameters_E1_1p5_5 : IssuingRoundParameters
     expectedParameters_E1_1p5_5 = IssuingRoundParameters with
    @@ -88,6 +109,16 @@ expectedParameters_E1_1p5_5 = IssuingRoundParameters with
       unclaimedValidatorRewards = 3746.5753424658
       unclaimedAppRewards = 72200.4261796042
       unclaimedSvRewards = 0.0
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E1_1p5_5_DevFund5 : IssuingRoundParameters
    +expectedParameters_E1_1p5_5_DevFund5 =
    +  expectedParameters_E1_1p5_5 with
    +    issuancePerSvRewardCoupon = 361.4916286149 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation)
    +    unclaimedValidatorRewards = 2034.2465753425 -- decreased by ~45 % (validator tranche strongly cap-bound; small pool accentuates drop)
    +    unclaimedAppRewards = 66302.4048706240 -- decreased by ~8.2 % (app tranche less constrained; close to linear scaling)
    +    unclaimedSvRewards = 0.0000000016 -- minor rounding drift (below significance threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 9512.9375951294
     
     expectedParameters_E1_5_10 : IssuingRoundParameters
     expectedParameters_E1_5_10 = IssuingRoundParameters with
    @@ -99,6 +130,17 @@ expectedParameters_E1_5_10 = IssuingRoundParameters with
       unclaimedValidatorRewards = 0.0000000017
       unclaimedAppRewards = 19879.2694063927
       unclaimedSvRewards = 0.0
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E1_5_10_DevFund5 : IssuingRoundParameters
    +expectedParameters_E1_5_10_DevFund5 =
    +  expectedParameters_E1_5_10 with
    +    issuancePerValidatorFaucetCoupon = 339.5662100457 -- decreased by ~5.6 % (slightly above 5 % due to faucet cap effects)
    +    issuancePerSvRewardCoupon = 90.3729071537 -- decreased by ~5 % (expected linear scaling)
    +    unclaimedValidatorRewards = 0.0 -- minor rounding drift (below significance threshold)
    +    unclaimedAppRewards = 16597.3059360731 -- decreased by ~16.5 % (cap constraints amplify reduction)
    +    unclaimedSvRewards = 0.0000000029 -- minor rounding drift (below significance threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 4756.4687975647
     
     expectedParameters_E1_10plus : IssuingRoundParameters
     expectedParameters_E1_10plus = IssuingRoundParameters with
    @@ -110,23 +152,45 @@ expectedParameters_E1_10plus = IssuingRoundParameters with
       unclaimedValidatorRewards = 0.0
       unclaimedAppRewards = 0.0000000152
       unclaimedSvRewards = 0.0000000023
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E1_10plus_DevFund5 : IssuingRoundParameters
    +expectedParameters_E1_10plus_DevFund5 =
    +  expectedParameters_E1_10plus with
    +    issuancePerFeaturedAppRewardCoupon = 70.3246004566 -- decreased by ~6 % (slightly above 5 % since both featured and unfeatured tranches are below cap)
    +    issuancePerValidatorFaucetCoupon = 140.7458143075 -- decreased by ~6.3 % (faucet below cap, reduced pool scales slightly more than linearly)
    +    issuancePerSvRewardCoupon = 22.5932267884 -- decreased by ~5 % (expected linear scaling)
    +    unclaimedAppRewards = 0.0000000084 -- decreased ~5 % (below rounding threshold)
    +    unclaimedSvRewards = 0.0000000033 -- minor rounding drift (below significance threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 2378.2343987823
    +
    +
     
     testE1 : Script ()
     testE1 = script do
       validateOpenMiningRoundSummary summaryExample1
     
    +  -- 0% Development Fund
       expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1
       expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1
       expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1
       expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1
       expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1
     
    +  -- 5% Development Fund
    +  -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies.
    +  expectedParameters_E1_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExample1
    +  expectedParameters_E1_0p5_1p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0p5_1p5) summaryExample1
    +  expectedParameters_E1_1p5_5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_1p5_5) summaryExample1
    +  expectedParameters_E1_5_10_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_5_10) summaryExample1
    +  expectedParameters_E1_10plus_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_10plus) summaryExample1
    +
     
     -- Example 2: all zeros
     -----------------------
     
    -summaryExamplNoActivity : OpenMiningRoundSummary
    -summaryExamplNoActivity = OpenMiningRoundSummary with
    +summaryExampleNoActivity : OpenMiningRoundSummary
    +summaryExampleNoActivity = OpenMiningRoundSummary with
       totalValidatorRewardCoupons = 0.0
       totalFeaturedAppRewardCoupons = 0.0
       totalUnfeaturedAppRewardCoupons = 0.0
    @@ -143,12 +207,28 @@ expectedParameters_NoActivity_0_0p5 = IssuingRoundParameters with
       unclaimedValidatorRewards = 38051.7503805175
       unclaimedAppRewards = 114155.2511415525
       unclaimedSvRewards = 608828.0060882801
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_NoActivity_0_0p5_DevFund5 : IssuingRoundParameters
    +expectedParameters_NoActivity_0_0p5_DevFund5 =
    +  expectedParameters_NoActivity_0_0p5 with
    +    issuancePerSvRewardCoupon = 578386.6057838661 -- decreased by ~5 % (expected linear scaling; no coupons active)
    +    unclaimedValidatorRewards = 36149.1628614916 -- decreased by ~5 % (fund allocation directly reduces total issuance)
    +    unclaimedAppRewards = 108447.4885844749 -- decreased by ~5 % (expected linear scaling)
    +    unclaimedSvRewards = 578386.6057838661 -- decreased by ~5 % (identical scaling since all rewards remain unclaimed)
    +    optAmuletsToIssueToDevelopmentFund = Some 38051.7503805175
    +
     
     testNoActivity : Script ()
     testNoActivity = script do
    -  validateOpenMiningRoundSummary summaryExamplNoActivity
    +  validateOpenMiningRoundSummary summaryExampleNoActivity
    +
    +  -- 0% Development Fund
    +  expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExampleNoActivity
     
    -  expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity
    +  -- 5% Development Fund
    +  -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies.
    +  expectedParameters_NoActivity_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExampleNoActivity
     
     
     -- Example 3: low activity
    @@ -173,18 +253,31 @@ expectedParameters_E3_0_0p5 = IssuingRoundParameters with
       unclaimedValidatorRewards = 9511.7503805175
       unclaimedAppRewards = 104095.2511415525
       unclaimedSvRewards = 0.0000000001
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_E3_0_0p5_DevFund5 : IssuingRoundParameters
    +expectedParameters_E3_0_0p5_DevFund5 =
    +  expectedParameters_E3_0_0p5 with
    +    issuancePerSvRewardCoupon = 289193.302891933 -- decreased by ~5 % (expected linear scaling; SV tranche not capped)
    +    unclaimedValidatorRewards = 7609.1628614916 -- decreased by ~20 % (validator tranche near cap, amplifying reduction)
    +    unclaimedAppRewards = 98387.4885844749 -- decreased by ~5.5 % (mostly linear scaling)
    +    optAmuletsToIssueToDevelopmentFund = Some 38051.7503805175
     
     testE4 : Script ()
     testE4 = script do
       validateOpenMiningRoundSummary summaryExample3
     
    +  -- 0% Development Fund
       expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3
     
    +  -- 5% Development Fund
    +  -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies.
    +  expectedParameters_E3_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExample3
    +
     
     -- Example 4: millions of SV reward weight
     -------------------------------------------
     
    -
     summaryExampleLargeSvRewardWeight : OpenMiningRoundSummary
     summaryExampleLargeSvRewardWeight = OpenMiningRoundSummary with
       totalValidatorRewardCoupons = 0.0
    @@ -203,13 +296,56 @@ expectedParameters_LargeSvRewardWeight_10plus = IssuingRoundParameters with
       unclaimedAppRewards = 35673.5159817352
       unclaimedValidatorRewards = 9512.9375951294
       unclaimedSvRewards = 0.0
    +  optAmuletsToIssueToDevelopmentFund = Some 0.0
    +
    +expectedParameters_LargeSvRewardWeight_10plus_DevFund5 : IssuingRoundParameters
    +expectedParameters_LargeSvRewardWeight_10plus_DevFund5 =
    +  expectedParameters_LargeSvRewardWeight_10plus with
    +    issuancePerSvRewardCoupon = 0.0009413844 -- decreased by ~5 % (expected linear scaling; SV weight dominates distribution)
    +    unclaimedValidatorRewards = 9037.2907153729 -- decreased by ~5 % (linear with total issuance)
    +    unclaimedAppRewards = 33889.8401826484 -- decreased by ~5 % (expected linear scaling)
    +    unclaimedSvRewards = 0.0001188433 -- minor rounding drift (below significance threshold)
    +    optAmuletsToIssueToDevelopmentFund = Some 2378.2343987823
     
     testLargeSvRewardWeight : Script ()
     testLargeSvRewardWeight = script do
    -  validateOpenMiningRoundSummary summaryExamplNoActivity
    +  validateOpenMiningRoundSummary summaryExampleNoActivity
    +
    +  -- 0% Development Fund
    +  expectedParameters_LargeSvRewardWeight_10plus ===
    +    computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight
    +
    +  -- 5% Development Fund
    +  -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies.
    +  expectedParameters_LargeSvRewardWeight_10plus_DevFund5 ===
    +    computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_10plus) summaryExampleLargeSvRewardWeight
    +
     
    -  let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight
    -  expectedParameters_LargeSvRewardWeight_10plus === actual
    +-- Example 5: Development Fund receives the full issuance
    +----------------------------------------------------------
    +
    +expectedParameters_AllIssuanceForFund : IssuingRoundParameters
    +expectedParameters_AllIssuanceForFund = IssuingRoundParameters with
    +  issuancePerValidatorRewardCoupon = 0.0
    +  issuancePerFeaturedAppRewardCoupon = 0.0
    +  issuancePerUnfeaturedAppRewardCoupon = 0.0
    +  issuancePerValidatorFaucetCoupon = 0.0
    +  issuancePerSvRewardCoupon = 0.0
    +  unclaimedAppRewards = 0.0
    +  unclaimedValidatorRewards = 0.0
    +  unclaimedSvRewards = 0.0
    +  optAmuletsToIssueToDevelopmentFund = Some 761035.0076103501
    +
    +testAllIssuanceForFund : Script ()
    +testAllIssuanceForFund = script do
    +  validateOpenMiningRoundSummary summaryExample3
     
    +  -- 100% Development Fund
    +  -- All issuance goes to the Development Fund; all reward-related fields are zero.
    +  let issuanceConfig_0_0p5_fundOne = issuanceConfig_0_0p5 with optDevelopmentFundPercentage = Some 1.0
    +  expectedParameters_AllIssuanceForFund === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5_fundOne summaryExample1
     
     
    +-- Utility: apply 5% Development Fund allocation
    +withDevFund5 : IssuanceConfig -> IssuanceConfig
    +withDevFund5 config = config with optDevelopmentFundPercentage = Some 0.05
    diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml b/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml
    index f7b47d47d6..8ecf56223a 100644
    --- a/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml
    +++ b/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml
    @@ -39,7 +39,7 @@ import Splice.Testing.Registries.AmuletRegistry.Parameters
     import Splice.Util
     
     -- Bootstrapping Amulet
    --------------------
    +------------------------
     
     
     -- | A type to hold the off-ledger information required to interact with the Amulet app.
    @@ -85,6 +85,19 @@ genericSetupApp dsoPrefix = do
       return app
     
     
    +-- Replacing AmuletConfig
    +--------------------------
    +
    +
    +replaceDevelopmentFundConfig : AmuletApp -> AmuletConfig Unit.USD -> Optional Party -> Optional Decimal -> Script ()
    +replaceDevelopmentFundConfig app baseConfig optDevelopmentFundManager optDevelopmentFundPercentage = do
    +  setAmuletConfig app baseConfig baseConfig with
    +    optDevelopmentFundManager
    +    issuanceCurve = baseConfig.issuanceCurve with
    +      initialValue = baseConfig.issuanceCurve.initialValue with
    +        optDevelopmentFundPercentage
    +
    +
     -- AmuletApp users
     --------------
     
    @@ -646,6 +659,26 @@ getAmuletConfig app = do
       now <- getTime
       pure $ getValueAsOf now amuletRules.configSchedule
     
    +setAmuletConfig : AmuletApp -> AmuletConfig Unit.USD -> AmuletConfig Unit.USD -> Script ()
    +setAmuletConfig app baseConfig newConfig = do
    +  Some (amuletRulesCid, _) <- queryAmuletRulesByKey app.dso
    +  void $ submit app.dso $
    +    exerciseCmd amuletRulesCid AmuletRules_SetConfig with
    +      newConfig
    +      baseConfig
    +
    +allocateDevelopmentFundCoupon
    +  : AmuletApp -> Party -> Party -> Decimal -> Time -> Text -> [ContractId UnclaimedDevelopmentFundCoupon]
    +  -> Script AmuletRules_AllocateDevelopmentFundCouponResult
    +allocateDevelopmentFundCoupon app fundManager beneficiary amount expiresAt reason unclaimedDevelopmentFundCouponCids = do
    +  submitExerciseAmuletRulesByKey app [fundManager] [] AmuletRules_AllocateDevelopmentFundCoupon with
    +    unclaimedDevelopmentFundCouponCids
    +    beneficiary
    +    amount
    +    expiresAt
    +    reason
    +    fundManager
    +
     -- Metadata verification
     ------------------------
     
    @@ -693,13 +726,16 @@ genericCheckTxMetadata extractMeta extractSummary app expectedKind sender body =
       mint <- case extractSummary result of
         None -> pure 0.0
         Some summary -> do
    -      let inputUnclaimedActivityRecordAmount = fromOptional 0.0 summary.inputUnclaimedActivityRecordAmount
    +      let
    +        inputUnclaimedActivityRecordAmount = fromOptional 0.0 summary.inputUnclaimedActivityRecordAmount
    +        inputDevelopmentFundAmountAmount = fromOptional 0.0 summary.inputDevelopmentFundAmount
           expectUnlessZero svRewardAmountMetaKey summary.inputSvRewardAmount
           expectUnlessZero appRewardAmountMetaKey summary.inputAppRewardAmount
           expectUnlessZero validatorRewardAmountMetaKey summary.inputValidatorRewardAmount
           expectUnlessZero unclaimedActivityRecordAmountMetaKey inputUnclaimedActivityRecordAmount
    +      expectUnlessZero developmentFundAmountMetaKey inputDevelopmentFundAmountAmount
           pure $ summary.inputAppRewardAmount + summary.inputValidatorRewardAmount + summary.inputSvRewardAmount +
    -        inputUnclaimedActivityRecordAmount
    +        inputUnclaimedActivityRecordAmount + inputDevelopmentFundAmountAmount
       let expectedBurn = totalHoldingsBefore + mint - totalHoldingsAfter
       -- mints are inferred, and show here as a negative burn
       if (expectedBurn < 0.0)
    diff --git a/daml/splice-amulet/daml.yaml b/daml/splice-amulet/daml.yaml
    index d0edcb5cbb..72d3f38f18 100644
    --- a/daml/splice-amulet/daml.yaml
    +++ b/daml/splice-amulet/daml.yaml
    @@ -6,7 +6,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-amulet
     source: daml
    -version: 0.1.14
    +version: 0.1.15
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml
    index d3366d4190..3891cc5af5 100644
    --- a/daml/splice-amulet/daml/Splice/Amulet.daml
    +++ b/daml/splice-amulet/daml/Splice/Amulet.daml
    @@ -81,9 +81,18 @@ data SvRewardCoupon_ArchiveAsBeneficiaryResult = SvRewardCoupon_ArchiveAsBenefic
     
     data UnclaimedActivityRecord_ArchiveAsBeneficiaryResult = UnclaimedActivityRecord_ArchiveAsBeneficiaryResult
     
    -data UnclaimedActivityRecord_DsoExpireResult = UnclaimedActivityRecord_DsoExpireResult with 
    +data UnclaimedActivityRecord_DsoExpireResult = UnclaimedActivityRecord_DsoExpireResult with
       unclaimedRewardCid : ContractId UnclaimedReward
     
    +data DevelopmentFundCoupon_WithdrawResult = DevelopmentFundCoupon_WithdrawResult with
    +  unclaimedDevelopmentFundCouponCid : ContractId UnclaimedDevelopmentFundCoupon
    +
    +data DevelopmentFundCoupon_RejectResult = DevelopmentFundCoupon_RejectResult with
    +  unclaimedDevelopmentFundCouponCid : ContractId UnclaimedDevelopmentFundCoupon
    +
    +data DevelopmentFundCoupon_DsoExpireResult = DevelopmentFundCoupon_DsoExpireResult with
    +  unclaimedDevelopmentFundCouponCid : ContractId UnclaimedDevelopmentFundCoupon
    +
     -- | A amulet, which can be locked and whose amount expires over time.
     --
     -- The expiry serves to charge an inactivity fee, and thereby ensures that the
    @@ -383,6 +392,65 @@ template SvRewardCoupon with
           do return SvRewardCoupon_ArchiveAsBeneficiaryResult
     
     
    +-- | A coupon recording an emission for the Development Fund from CIP-0082 that
    +-- was not yet assigned to a specific beneficiary.
    +template UnclaimedDevelopmentFundCoupon
    +  with
    +    dso : Party
    +    amount : Decimal -- ^ The total amount of `Amulet` to mint on collection.
    +  where
    +    signatory dso
    +    ensure amount > 0.0
    +
    +-- | A coupon recording an emission for the Development Fund under CIP-0082,
    +-- which can be collected by the designated beneficiary.
    +template DevelopmentFundCoupon
    +  with
    +    dso : Party
    +    beneficiary : Party -- ^ The owner of the `Amulet` to be minted.
    +    fundManager : Party
    +      -- ^ The party that executed the assignment of the coupon to the beneficiary
    +      -- so they can mint from the development fund.
    +    amount : Decimal -- ^ The total amount of `Amulet` to mint on collection.
    +    expiresAt : Time -- ^ Until when the minting can be completed.
    +    reason : Text -- ^ Reason for the emission of the coupon.
    +  where
    +    ensure amount > 0.0
    +
    +    signatory dso
    +
    +    -- The beneficiary is an observer of the coupon, as they need to be able to claim it.
    +    observer beneficiary
    +
    +    -- The fundManager is an observer so they can see both the transaction creating and the one archiving the coupon.
    +    observer fundManager
    +
    +    choice DevelopmentFundCoupon_Withdraw : DevelopmentFundCoupon_WithdrawResult
    +      with
    +        reason : Text -- ^ Reason for withdrawing the coupon.
    +      controller fundManager
    +      do
    +        assertWithinDeadline "DevelopmentFundCoupon.expiresAt" expiresAt
    +        unclaimedDevelopmentFundCouponCid <- create UnclaimedDevelopmentFundCoupon with dso; amount
    +        return DevelopmentFundCoupon_WithdrawResult with unclaimedDevelopmentFundCouponCid
    +
    +    choice DevelopmentFundCoupon_Reject : DevelopmentFundCoupon_RejectResult
    +      with
    +        reason : Text -- ^ Reason for rejecting the coupon.
    +      controller beneficiary
    +      do
    +        assertWithinDeadline "DevelopmentFundCoupon.expiresAt" expiresAt
    +        unclaimedDevelopmentFundCouponCid <- create UnclaimedDevelopmentFundCoupon with dso; amount
    +        return DevelopmentFundCoupon_RejectResult with unclaimedDevelopmentFundCouponCid
    +
    +    choice DevelopmentFundCoupon_DsoExpire : DevelopmentFundCoupon_DsoExpireResult
    +      controller dso
    +      do
    +        assertDeadlineExceeded "DevelopmentFundCoupon.expiresAt" expiresAt
    +        unclaimedDevelopmentFundCouponCid <- create UnclaimedDevelopmentFundCoupon with dso; amount
    +        pure DevelopmentFundCoupon_DsoExpireResult with unclaimedDevelopmentFundCouponCid
    +
    +
     -- | Rewards that have not been claimed and are thus at the disposal of the foundation.
     template UnclaimedReward with
         dso : Party
    @@ -392,29 +460,29 @@ template UnclaimedReward with
     
         signatory dso
     
    --- | A record of activity that can be minted by the beneficiary. 
    --- Note that these do not come out of the per-round issuance but are instead created by burning 
    --- UnclaimedRewardCoupon as defined through a vote by the SVs. That's also why expiry is a separate 
    +-- | A record of activity that can be minted by the beneficiary.
    +-- Note that these do not come out of the per-round issuance but are instead created by burning
    +-- UnclaimedRewardCoupon as defined through a vote by the SVs. That's also why expiry is a separate
     -- time-based expiry instead of being tied to a round like the other activity records.
     template UnclaimedActivityRecord
       with
         dso : Party
         beneficiary : Party -- ^ The owner of the `Amulet` to be minted.
         amount : Decimal -- ^ The amount of `Amulet` to be minted.
    -    reason : Text -- ^ A reason to mint the `Amulet`. 
    -    expiresAt : Time -- ^ Selected timestamp defining the lifetime of the contract. 
    -  where 
    +    reason : Text -- ^ A reason to mint the `Amulet`.
    +    expiresAt : Time -- ^ Selected timestamp defining the lifetime of the contract.
    +  where
         signatory dso
         observer beneficiary
         ensure amount > 0.0
     
         choice UnclaimedActivityRecord_DsoExpire : UnclaimedActivityRecord_DsoExpireResult
           controller dso
    -      do 
    +      do
             assertDeadlineExceeded "UnclaimedActivityRecord.expiresAt" expiresAt
             unclaimedRewardCid <- create UnclaimedReward with dso; amount
             pure UnclaimedActivityRecord_DsoExpireResult with unclaimedRewardCid
    -  
    +
     
     requireAmuletExpiredForAllOpenRounds : ContractId OpenMiningRound -> Amulet -> Update ()
     requireAmuletExpiredForAllOpenRounds roundCid amulet = do
    @@ -456,4 +524,13 @@ instance HasCheckedFetch FeaturedAppActivityMarker ForDso where
       contractGroupId FeaturedAppActivityMarker {..} = ForDso with dso
     
     instance HasCheckedFetch UnclaimedActivityRecord ForOwner where
    -  contractGroupId UnclaimedActivityRecord{..} = ForOwner with dso; owner = beneficiary
    \ No newline at end of file
    +  contractGroupId UnclaimedActivityRecord{..} = ForOwner with dso; owner = beneficiary
    +
    +instance HasCheckedFetch UnclaimedDevelopmentFundCoupon ForDso where
    +  contractGroupId UnclaimedDevelopmentFundCoupon{..} = ForDso with dso
    +
    +instance HasCheckedFetch DevelopmentFundCoupon ForOwner where
    +  contractGroupId DevelopmentFundCoupon{..} = ForOwner with dso; owner = beneficiary
    +
    +instance HasCheckedFetch DevelopmentFundCoupon ForDso where
    +  contractGroupId DevelopmentFundCoupon{..} = ForDso with dso
    diff --git a/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml b/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml
    index 1b8f738c20..bd339412e3 100644
    --- a/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml
    +++ b/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml
    @@ -43,7 +43,7 @@ nonZeroMetadata k n m
     
     -- | Add an metadata entry for an optional value if it is non-zero number.
     optionalNonZeroMetadata : (Eq a, Additive a, Show a) => Text -> Optional a -> TextMap Text -> TextMap Text
    -optionalNonZeroMetadata k optN m = 
    +optionalNonZeroMetadata k optN m =
       case optN of
         None -> m
         Some n -> nonZeroMetadata k n m
    @@ -75,6 +75,9 @@ appRewardBeneficiariesMetaKey = amuletPrefix <> "app-reward-beneficiaries"
     appRewardBeneficiaryWeightsMetaKey : Text
     appRewardBeneficiaryWeightsMetaKey = amuletPrefix <> "app-reward-beneficiary-weights"
     
    +developmentFundAmountMetaKey : Text
    +developmentFundAmountMetaKey = amuletPrefix <> "development-fund"
    +
     
     -- Splice API Metadata keys
     ---------------------------
    diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml
    index abb4dc9af0..c5c9f1bf4d 100644
    --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml
    +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml
    @@ -45,6 +45,8 @@ data AmuletConfig unit = AmuletConfig with
           -- that should be used for command submissions.
         transferPreapprovalFee : Optional Decimal -- ^ Fee for keeping a transfer pre-approval around.
         featuredAppActivityMarkerAmount : Optional Decimal -- ^ $-amount used for the conversion from FeaturedAppActivityMarker -> AppRewardCoupon
    +    optDevelopmentFundManager : Optional Party
    +      -- ^ Party authorized to manage and allocate minting rights from the Development Fund.
       deriving (Eq, Show)
     
     -- $1/year specified as a daily rate
    @@ -120,6 +122,7 @@ instance Patchable (AmuletConfig USD) where
         packageConfig = patch new.packageConfig base.packageConfig current.packageConfig
         transferPreapprovalFee = patch new.transferPreapprovalFee base.transferPreapprovalFee current.transferPreapprovalFee
         featuredAppActivityMarkerAmount = patch new.featuredAppActivityMarkerAmount base.featuredAppActivityMarkerAmount current.featuredAppActivityMarkerAmount
    +    optDevelopmentFundManager = patch new.optDevelopmentFundManager base.optDevelopmentFundManager current.optDevelopmentFundManager
     
     instance Patchable (TransferConfig USD) where
       patch new base current = TransferConfig with
    diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml
    index 392e25fa9f..9ff3a16ea4 100644
    --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml
    +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml
    @@ -1,6 +1,7 @@
     -- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
     -- SPDX-License-Identifier: Apache-2.0
     
    +{-# LANGUAGE MultiWayIf #-}
     module Splice.AmuletRules where
     
     import Prelude hiding (forA)
    @@ -64,6 +65,7 @@ data AmuletRules_AdvanceOpenMiningRoundsResult = AmuletRules_AdvanceOpenMiningRo
     
     data AmuletRules_MiningRound_StartIssuingResult = AmuletRules_MiningRound_StartIssuingResult with
         issuingRoundCid : ContractId IssuingMiningRound
    +    unclaimedDevelopmentFundCouponCid : Optional (ContractId UnclaimedDevelopmentFundCoupon)
     
     data AmuletRules_MiningRound_CloseResult = AmuletRules_MiningRound_CloseResult with
         closedRoundCid : ContractId ClosedMiningRound
    @@ -76,6 +78,13 @@ data AmuletRules_ClaimExpiredRewardsResult = AmuletRules_ClaimExpiredRewardsResu
     data AmuletRules_MergeUnclaimedRewardsResult = AmuletRules_MergeUnclaimedRewardsResult with
         unclaimedRewardCid : ContractId UnclaimedReward
     
    +data AmuletRules_MergeUnclaimedDevelopmentFundCouponsResult = AmuletRules_MergeUnclaimedDevelopmentFundCouponsResult with
    +    unclaimedDevelopmentFundCouponCid : ContractId UnclaimedDevelopmentFundCoupon
    +
    +data AmuletRules_AllocateDevelopmentFundCouponResult = AmuletRules_AllocateDevelopmentFundCouponResult with
    +    developmentFundCouponCid : ContractId DevelopmentFundCoupon
    +    optUnclaimedDevelopmentFundCouponCid : Optional (ContractId UnclaimedDevelopmentFundCoupon)
    +
     data AmuletRules_SetConfigResult = AmuletRules_SetConfigResult with
         newAmuletRules : ContractId AmuletRules
     
    @@ -450,6 +459,12 @@ template AmuletRules
                 dso
                 amount = totalUnclaimedRewards
     
    +        -- record unclaimed development fund coupon contract
    +        unclaimedDevelopmentFundCouponCid <-
    +          case params.optAmuletsToIssueToDevelopmentFund of
    +            Some amount | amount > 0.0 -> Some <$> create UnclaimedDevelopmentFundCoupon with dso; amount
    +            _ -> pure None
    +
             -- create issuing round
             now <- getTime
             let tickDuration = miningRound.tickDuration
    @@ -579,6 +594,68 @@ template AmuletRules
     
             return AmuletRules_MergeUnclaimedRewardsResult with ..
     
    +    nonconsuming choice AmuletRules_MergeUnclaimedDevelopmentFundCoupons : AmuletRules_MergeUnclaimedDevelopmentFundCouponsResult
    +      -- ^ Batch merge of unclaimed development fund coupons
    +      with
    +        unclaimedDevelopmentFundCouponCids : [ContractId UnclaimedDevelopmentFundCoupon]
    +      controller dso
    +      do
    +        require "More than one unclaimed development fund coupon contract" (length unclaimedDevelopmentFundCouponCids > 1)
    +
    +        -- archive all given coupons
    +        archivedAmounts <- forA unclaimedDevelopmentFundCouponCids $ \unclaimedDevelopmentFundCouponCid -> do
    +          unclaimedDevelopmentFundCoupon <- fetchAndArchive (ForDso with dso) unclaimedDevelopmentFundCouponCid
    +          pure unclaimedDevelopmentFundCoupon.amount
    +
    +        -- create a new unclaimed development fund coupon over the total
    +        unclaimedDevelopmentFundCouponCid <- create UnclaimedDevelopmentFundCoupon with
    +          dso
    +          amount = sum archivedAmounts
    +
    +        return AmuletRules_MergeUnclaimedDevelopmentFundCouponsResult with ..
    +
    +    nonconsuming choice AmuletRules_AllocateDevelopmentFundCoupon : AmuletRules_AllocateDevelopmentFundCouponResult
    +      -- ^ Allows the Development Fund manager to allocate a specified amount from a
    +      -- collection of UnclaimedDevelopmentFundCoupons to a beneficiary, generating a
    +      -- DevelopmentFundCoupon and producing a leftover unclaimed coupon if applicable.
    +      with
    +        unclaimedDevelopmentFundCouponCids : [ContractId UnclaimedDevelopmentFundCoupon]
    +        beneficiary : Party
    +        amount : Decimal
    +        expiresAt : Time
    +        reason : Text
    +        fundManager : Party
    +      controller fundManager
    +      do
    +        -- Verify expiry
    +        assertWithinDeadline "DevelopmentFundCoupon.expiresAt" expiresAt
    +
    +        -- Verify fundManager
    +        configUsd <- getValueAsOfLedgerTime configSchedule
    +        case configUsd.optDevelopmentFundManager of
    +          None -> abort "DevelopmentFundCoupon cannot be allocated without a Development Fund manager configured"
    +          Some developmentFundManager ->
    +            require ("controller is the configured Development Fund manager: " <> show developmentFundManager) $
    +              developmentFundManager == fundManager
    +
    +        -- Verify the amount and create a leftover UnclaimedDevelopmentFundCoupon if needed
    +        totalUnclaimedDevelopmentFundCoupons <- sum <$> forA unclaimedDevelopmentFundCouponCids \cid -> do
    +          coupon <- fetchAndArchive (ForDso with dso) cid
    +          pure coupon.amount
    +        let leftover = totalUnclaimedDevelopmentFundCoupons - amount
    +        optUnclaimedDevelopmentFundCouponCid <-
    +          if | leftover < 0.0 -> abort $ "insufficient amount to cover the requested allocation: " <> show (negate leftover)
    +             | leftover == 0.0 -> pure None
    +             | otherwise -> Some <$> create UnclaimedDevelopmentFundCoupon with dso; amount = leftover
    +
    +        -- record development fund coupon contract
    +        developmentFundCouponCid <- create DevelopmentFundCoupon with
    +          dso; beneficiary; fundManager; amount; expiresAt; reason
    +
    +        return AmuletRules_AllocateDevelopmentFundCouponResult with
    +          developmentFundCouponCid
    +          optUnclaimedDevelopmentFundCouponCid
    +
         -- This allows fetchByKey-style fetches if you have readAs
         -- but not actAs claims.
         nonconsuming choice AmuletRules_Fetch: AmuletRules
    @@ -749,6 +826,7 @@ executeTransfer config context dso t = do
               nonZeroMetadata appRewardAmountMetaKey summary.inputAppRewardAmount $
               nonZeroMetadata validatorRewardAmountMetaKey summary.inputValidatorRewardAmount $
               optionalNonZeroMetadata unclaimedActivityRecordAmountMetaKey summary.inputUnclaimedActivityRecordAmount $
    +          optionalNonZeroMetadata developmentFundAmountMetaKey summary.inputDevelopmentFundAmount $
               TextMap.empty
     
       return TransferResult with
    @@ -778,6 +856,9 @@ data TransferInputsSummary = TransferInputsSummary with
         totalUnclaimedActivityRecordAmount : Optional Decimal
           -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker
           -- on package upload because `TransferInputsSummary` is serializable.
    +    totalDevelopmentFundAmount : Optional Decimal
    +      -- ^ Note: Same rationale as above — made optional to ensure compatibility with
    +      -- the upgrade checker on package upload because `TransferInputsSummary` is serializable.
       deriving (Eq, Show)
     
     type TransferOutputsSummary = [PreprocessedTransferOutput]
    @@ -836,6 +917,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
         foldlA (summarizeAndConsumeInput csum.openRound.round) initialSummary inps
       where
         forOwner = ForOwner with dso; owner = sender
    +    forDso = ForDso with dso
     
         initialSummary = TransferInputsSummary with
           totalAmuletAmount = 0.0
    @@ -847,6 +929,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
           amountArchivedAsOfRoundZero = 0.0
           changeToHoldingFeesRate = 0.0
           totalUnclaimedActivityRecordAmount = Some 0.0
    +      totalDevelopmentFundAmount = Some 0.0
     
         summarizeAndConsumeInput _round s (InputAmulet amuletCid) = do
           amulet <- fetchAndArchive forOwner amuletCid
    @@ -863,6 +946,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero + getValueAsOfRound0 amulet.amount
             changeToHoldingFeesRate = s.changeToHoldingFeesRate - amulet.amount.ratePerRound.rate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
         summarizeAndConsumeInput _round s (InputAppRewardCoupon couponCid) = do
           coupon <- fetchAndArchive forOwner couponCid
    @@ -881,10 +965,11 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
         summarizeAndConsumeInput _round s (InputValidatorRewardCoupon couponCid) = do
           -- we must and do use the validator right to archive the coupon of the user
    -      coupon <- fetchButArchiveLater (ForDso with dso) couponCid
    +      coupon <- fetchButArchiveLater forDso couponCid
           do
             rightCid <- getValidatorRight csum coupon.user
             exercise couponCid ValidatorRewardCoupon_ArchiveAsValidator with
    @@ -902,6 +987,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
         summarizeAndConsumeInput _round s (InputSvRewardCoupon couponCid) = do
           -- we use the SvRewardCoupon_ArchiveAsBeneficiary choice to signal the archival of the coupon
    @@ -920,6 +1006,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
         summarizeAndConsumeInput _round s (InputValidatorLivenessActivityRecord recordCid) = do
           record <- fetchAndArchive forOwner recordCid
    @@ -936,6 +1023,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
         summarizeAndConsumeInput _round s (ExtTransferInput _dummyUnitField optInputValidatorFaucetCoupon) = do
           optional (pure s) (summarizeAndConsumeValidatorFaucetInput s) optInputValidatorFaucetCoupon
    @@ -954,6 +1042,22 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
    +
    +    summarizeAndConsumeInput _round s (InputDevelopmentFundCoupon couponCid) = do
    +      coupon <- fetchAndArchive forOwner couponCid
    +      assertWithinDeadline "DevelopmentFundCoupon.expiresAt" coupon.expiresAt
    +      return TransferInputsSummary with
    +        totalAmuletAmount = s.totalAmuletAmount
    +        totalAppRewardAmount = s.totalAppRewardAmount
    +        totalValidatorRewardAmount = s.totalValidatorRewardAmount
    +        totalUnclaimedActivityRecordAmount = s.totalUnclaimedActivityRecordAmount
    +        totalValidatorFaucetAmount = s.totalValidatorFaucetAmount
    +        totalSvRewardAmount = s.totalSvRewardAmount
    +        totalHoldingFees = s.totalHoldingFees
    +        amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
    +        changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = (+ coupon.amount) <$> s.totalDevelopmentFundAmount
     
         summarizeAndConsumeValidatorFaucetInput s couponCid = do
           coupon <- fetchAndArchive forOwner couponCid
    @@ -970,6 +1074,7 @@ summarizeAndConsumeInputs csum dso sender inps = do
             totalHoldingFees = s.totalHoldingFees
             amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero
             changeToHoldingFeesRate = s.changeToHoldingFeesRate
    +        totalDevelopmentFundAmount = s.totalDevelopmentFundAmount
     
     -- | Deduplicate lock-holders to store them and charge for them at most once
     dedupOutputLockHolders : TransferOutput -> TransferOutput
    @@ -1029,6 +1134,7 @@ summarizeTransfer sender openRound transferConfigAmulet inp preprocessedOutputs
           + fromOptional 0.0 inp.totalUnclaimedActivityRecordAmount
           + inp.totalValidatorFaucetAmount
           + inp.totalSvRewardAmount
    +      + fromOptional 0.0 inp.totalDevelopmentFundAmount
           - totalOutputAmount - sum outputFees
         senderChangeFee = min transferConfigAmulet.createFee.fee leftOverAmount
         senderChangeAmount = leftOverAmount - senderChangeFee
    @@ -1054,6 +1160,7 @@ summarizeTransfer sender openRound transferConfigAmulet inp preprocessedOutputs
           inputUnclaimedActivityRecordAmount = inp.totalUnclaimedActivityRecordAmount
           inputValidatorFaucetAmount = Some inp.totalValidatorFaucetAmount
           inputSvRewardAmount = inp.totalSvRewardAmount
    +      inputDevelopmentFundAmount = inp.totalDevelopmentFundAmount
           holdingFees = inp.totalHoldingFees
           outputFees
           senderChangeFee
    @@ -1245,6 +1352,7 @@ data TransferInput
             -- ^ Added in CIP-3. Optional validator faucet coupon input into this transfer.
       | InputValidatorLivenessActivityRecord (ContractId ValidatorLivenessActivityRecord)
       | InputUnclaimedActivityRecord (ContractId UnclaimedActivityRecord)
    +  | InputDevelopmentFundCoupon (ContractId DevelopmentFundCoupon)
       deriving (Eq, Ord, Show)
     
     -- | Smart constructor for inputing validator faucet coupons into a transfer.
    @@ -1348,6 +1456,9 @@ data TransferSummary = TransferSummary with
         inputUnclaimedActivityRecordAmount : Optional Decimal
           -- ^ Total amount of unclaimed activity record issuance input into this transfer.
           -- Note: Made optional as the addition of this field is checked by the upgrade checker.
    +    inputDevelopmentFundAmount : Optional Decimal
    +      -- ^ Total amount of development fund coupon issuance input into this transfer.
    +      -- Note: Made optional as the addition of this field is checked by the upgrade checker.
       deriving (Show, Eq)
     
     data BalanceChange = BalanceChange with
    diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml
    index 91da289785..d14d8f07b0 100644
    --- a/daml/splice-amulet/daml/Splice/Issuance.daml
    +++ b/daml/splice-amulet/daml/Splice/Issuance.daml
    @@ -25,6 +25,8 @@ data IssuanceConfig = IssuanceConfig with
         optValidatorFaucetCap : Optional Decimal
           -- ^ Maximal amount in $ for the per-validator issuance of validator faucet coupons;
           -- Introduced as part of CIP-3. Defaults to 2.85 USD.
    +    optDevelopmentFundPercentage : Optional Decimal
    +      -- ^ Percentage of each mint emission allocated to the Development Fund under CIP-0082.
       deriving (Eq, Show)
     
     -- | Getter with the right default value for the validator faucet cap.
    @@ -45,6 +47,7 @@ validIssuanceConfig this@IssuanceConfig{..} =
         && featuredAppRewardCap >= 0.0
         && unfeaturedAppRewardCap >= 0.0
         && getValidatorFaucetCap this >= 0.0
    +    && optional True (\pct -> pct >= 0.0 && pct <= 1.0) optDevelopmentFundPercentage
     
     
     -- computation of issuance per round
    @@ -73,6 +76,9 @@ data IssuingRoundParameters = IssuingRoundParameters with
         unclaimedValidatorRewards : Decimal
         unclaimedSvRewards : Decimal -- ^ Can be non-zero due to rounding, or no SV having had the chance to claim their coupons.
         issuancePerValidatorFaucetCoupon : Decimal
    +    optAmuletsToIssueToDevelopmentFund : Optional Decimal
    +      -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker
    +      -- on package upload because `IssuingRoundParameters` is serializable.
       deriving (Eq, Show)
     
     validateOpenMiningRoundSummary : CanAssert m => OpenMiningRoundSummary -> m ()
    @@ -94,9 +100,14 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary =
           unclaimedAppRewards = featuredAppIssuance.unclaimedRewards
           unclaimedSvRewards
           issuancePerValidatorFaucetCoupon = validatorFaucetIssuance.issuancePerCoupon
    +      optAmuletsToIssueToDevelopmentFund = Some amuletsToIssueToDevelopmentFund
       where
    +    developmentFundPercentage = fromOptional 0.05 config.optDevelopmentFundPercentage
    +
         amuletsToIssueToSvs =
    -      amuletsToIssueInRound - validatorRewardIssuance.rewardsToIssue - unfeaturedAppIssuance.rewardsToIssue
    +      adjustedAmuletsToIssueInRound
    +        - validatorRewardIssuance.rewardsToIssue
    +        - unfeaturedAppIssuance.rewardsToIssue
     
         issuancePerSvRewardCoupon =
           if summary.totalSvRewardWeight == 0
    @@ -114,8 +125,11 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary =
           intToDecimal (convertRelTimeToMicroseconds tickDuration)
         amuletsToIssueInRound = config.amuletToIssuePerYear / roundsPerYear
     
    +    amuletsToIssueToDevelopmentFund = amuletsToIssueInRound * developmentFundPercentage
    +    adjustedAmuletsToIssueInRound = amuletsToIssueInRound - amuletsToIssueToDevelopmentFund
    +
         validatorRewardIssuance = computeIssuanceTranche
    -      (amuletsToIssueInRound * config.validatorRewardPercentage)
    +      (adjustedAmuletsToIssueInRound * config.validatorRewardPercentage)
           config.validatorRewardCap
           summary.totalValidatorRewardCoupons
     
    @@ -126,7 +140,7 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary =
           (intToDecimal $ getTotalValidatorFaucetCoupons summary)
     
         unfeaturedAppIssuance = computeIssuanceTranche
    -      (amuletsToIssueInRound * config.appRewardPercentage)
    +      (adjustedAmuletsToIssueInRound * config.appRewardPercentage)
           config.unfeaturedAppRewardCap
           (summary.totalFeaturedAppRewardCoupons + summary.totalUnfeaturedAppRewardCoupons)
     
    @@ -179,3 +193,4 @@ instance Patchable IssuanceConfig where
         featuredAppRewardCap = patch new.featuredAppRewardCap base.featuredAppRewardCap current.featuredAppRewardCap
         unfeaturedAppRewardCap = patch new.unfeaturedAppRewardCap base.unfeaturedAppRewardCap current.unfeaturedAppRewardCap
         optValidatorFaucetCap = patch new.optValidatorFaucetCap base.optValidatorFaucetCap current.optValidatorFaucetCap
    +    optDevelopmentFundPercentage = patch new.optDevelopmentFundPercentage base.optDevelopmentFundPercentage current.optDevelopmentFundPercentage
    diff --git a/daml/splice-dso-governance-test/daml.yaml b/daml/splice-dso-governance-test/daml.yaml
    index e70244196f..428dc24cc7 100644
    --- a/daml/splice-dso-governance-test/daml.yaml
    +++ b/daml/splice-dso-governance-test/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-dso-governance-test
     source: daml
    -version: 0.1.24
    +version: 0.1.25
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml
    index 17abe747c3..8187ce50ad 100644
    --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml
    +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml
    @@ -9,12 +9,14 @@ import DA.Assert
     import DA.Foldable (forA_)
     import DA.List
     import qualified DA.Map as Map
    +import DA.Optional (fromOptional)
     import qualified DA.Set as Set
     import qualified DA.Text as T
     import Daml.Script
     import DA.Time
     
     import Splice.Amulet
    +import Splice.AmuletConfig (AmuletConfig(..), USD)
     import Splice.AmuletRules
     import Splice.Issuance
     import Splice.Round
    @@ -38,24 +40,26 @@ bpsMultiplier : Int
     bpsMultiplier = 10000
     
     initMainNet : Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initMainNet = initDecentralizedSynchronizer False
    +initMainNet = initDecentralizedSynchronizer False None
     
     
     initMainNetWithAmuletPrice : Decimal -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initMainNetWithAmuletPrice = initDecentralizedSynchronizerWithAmuletPrice False 0
    +initMainNetWithAmuletPrice amuletPrice = initDecentralizedSynchronizerWithAmuletPrice False 0 amuletPrice None
     
     initDevNet : Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initDevNet = initDecentralizedSynchronizer True
    +initDevNet = initDecentralizedSynchronizer True None
     
    +initDevNetWithAmuletConfig : AmuletConfig USD -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    +initDevNetWithAmuletConfig amuletConfig = initDecentralizedSynchronizer True (Some amuletConfig)
     
    -initDecentralizedSynchronizer : Bool -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initDecentralizedSynchronizer isDevNet = initDecentralizedSynchronizerWithAmuletPrice isDevNet 0 1.0
    +initDecentralizedSynchronizer : Bool -> Optional (AmuletConfig USD) -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    +initDecentralizedSynchronizer isDevNet optAmuletConfig = initDecentralizedSynchronizerWithAmuletPrice isDevNet 0 1.0 optAmuletConfig
     
     initDecentralizedSynchronizerWithNonZeroRound : Bool -> Int -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initDecentralizedSynchronizerWithNonZeroRound isDevNet initialRound = initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound 1.0
    +initDecentralizedSynchronizerWithNonZeroRound isDevNet initialRound = initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound 1.0 None
     
    -initDecentralizedSynchronizerWithAmuletPrice : Bool -> Int -> Decimal -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    -initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice = do
    +initDecentralizedSynchronizerWithAmuletPrice : Bool -> Int -> Decimal -> Optional (AmuletConfig USD) -> Script (AmuletApp, Party, (Party, Party, Party, Party))
    +initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice optAmuletConfig = do
       [sv1, sv2, sv3, sv4] <- forA ["sv1", "sv2", "sv3", "sv4"] allocateParty
     
       dso <- allocateParty "dso-party"
    @@ -82,7 +86,7 @@ initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice =
             decentralizedSynchronizer = initialDsoDecentralizedSynchronizerConfig
             nextScheduledSynchronizerUpgrade = None
             voteCooldownTime = None -- use default value of 1 minute
    -  let amuletConfig = defaultAmuletConfig
    +  let amuletConfig = fromOptional defaultAmuletConfig optAmuletConfig
       let ansRulesConfig = defaultAnsRulesConfig
     
       now <- getTime
    diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml
    index 5d08b8d107..045a1e36b3 100644
    --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml
    +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml
    @@ -3,6 +3,7 @@
     
     module Splice.Scripts.TestDecentralizedAutomation where
     
    +import DA.Action (replicateA_)
     import DA.Assert
     import DA.Foldable (forA_)
     import DA.List()
    @@ -26,6 +27,7 @@ import Splice.Scripts.DsoTestUtils
     import Splice.Ans
     import Splice.Scripts.AnsRulesParameters
     import Splice.Scripts.TestTransferPreapproval
    +import Splice.Testing.Registries.AmuletRegistry.Parameters
     import Splice.Wallet.Subscriptions
     
     
    @@ -53,6 +55,96 @@ testUnclaimedRewardsMerging = do
       pure ()
     
     
    +-- Development Fund
    +--------------------
    +
    +testUnclaimedDevelopmentFundCouponsMerging : Script ()
    +testUnclaimedDevelopmentFundCouponsMerging = do
    +  let
    +    -- 5% Development Fund
    +    amuletConfig =
    +      defaultAmuletConfig with
    +        issuanceCurve = defaultAmuletConfig.issuanceCurve with
    +          initialValue = defaultAmuletConfig.issuanceCurve.initialValue with
    +            optDevelopmentFundPercentage = Some 0.05
    +  (app, _, (sv1, _, _, _)) <- initDevNetWithAmuletConfig amuletConfig
    +
    +  -- Mint 5 unclaimed development fund coupons
    +  replicateA_ 5 $ runNextIssuanceD app 1.0
    +
    +  [(amuletRulesCid, _)] <- query @AmuletRules app.dso
    +  unclaimedDevelopmentFundCouponCids@(cid1 :: _) <- fmap fst <$> query @UnclaimedDevelopmentFundCoupon app.dso
    +  length unclaimedDevelopmentFundCouponCids === 5
    +
    +  -- Unhappy path - requires more than one development fund coupon contract.
    +  dsoDelegateSubmitsMustFail app $ \cid -> exerciseCmd cid $
    +    DsoRules_MergeUnclaimedDevelopmentFundCoupons with
    +      amuletRulesCid
    +      choiceArg = AmuletRules_MergeUnclaimedDevelopmentFundCoupons with
    +        unclaimedDevelopmentFundCouponCids = [cid1]
    +      sv = sv1
    +
    +  -- Happy path
    +  dsoDelegateSubmits app $ \cid -> exerciseCmd cid $
    +    DsoRules_MergeUnclaimedDevelopmentFundCoupons with
    +      amuletRulesCid
    +      choiceArg = AmuletRules_MergeUnclaimedDevelopmentFundCoupons with
    +        unclaimedDevelopmentFundCouponCids
    +      sv = sv1
    +
    +  unclaimedDevelopmentFundCoupons <- query @UnclaimedDevelopmentFundCoupon app.dso
    +  length unclaimedDevelopmentFundCoupons === 1
    +
    +  pure ()
    +
    +testDevelopmentFundCouponExpiry : Script ()
    +testDevelopmentFundCouponExpiry = do
    +  fundManager <- allocateParty "FundManager"
    +  let
    +    -- 5% Development Fund
    +    amuletConfig =
    +      defaultAmuletConfig with
    +        optDevelopmentFundManager = Some fundManager
    +        issuanceCurve = defaultAmuletConfig.issuanceCurve with
    +          initialValue = defaultAmuletConfig.issuanceCurve.initialValue with
    +            optDevelopmentFundPercentage = Some 0.05
    +  (app, _, (sv1, _, _, _)) <- initDevNetWithAmuletConfig amuletConfig
    +  [(dsoRulesCid, _)] <- query @DsoRules app.dso
    +
    +  -- Mint 1 unclaimed development fund coupon
    +  runNextIssuanceD app 1.0
    +  [(unclaimedDevelopmentFundCouponCid, unclaimedDevelopmentFundCoupon)] <- query @UnclaimedDevelopmentFundCoupon app.dso
    +
    +  -- Allocate a development fund coupon
    +  now <- getTime
    +  let
    +    expiresAt = addRelTime now (hours 1)
    +    amount = unclaimedDevelopmentFundCoupon.amount
    +    reason = "Alice fixed issue XXX"
    +  alice <- setupUser app "alice" app.dso
    +  developmentFundCouponCid <- (.developmentFundCouponCid) <$>
    +    allocateDevelopmentFundCoupon app fundManager alice.primaryParty amount expiresAt reason [unclaimedDevelopmentFundCouponCid]
    +
    +  -- Unhappy - expiresAt has not been reached
    +  submitMultiMustFail [sv1] [app.dso] do
    +    exerciseCmd dsoRulesCid DsoRules_ExpireDevelopmentFundCoupon with
    +      developmentFundCouponCid
    +      sv = sv1
    +
    +  -- Happy
    +  setTime $ addRelTime expiresAt (minutes 1)
    +  unclaimedDevelopmentFundCouponCid <- (.unclaimedDevelopmentFundCouponCid) . (.result) <$>
    +    submitMulti [sv1] [app.dso] do
    +      exerciseCmd dsoRulesCid DsoRules_ExpireDevelopmentFundCoupon with
    +        developmentFundCouponCid
    +        sv = sv1
    +  Some unclaimedDevelopmentFundCoupon <- queryContractId app.dso unclaimedDevelopmentFundCouponCid
    +  unclaimedDevelopmentFundCoupon === UnclaimedDevelopmentFundCoupon with
    +    dso = app.dso
    +    amount
    +
    +  pure ()
    +
     
     -- Testing confirmations
     ------------------------
    diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml
    index c71be96ff0..da4495b672 100644
    --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml
    +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml
    @@ -730,6 +730,7 @@ testAmuletRulesTickDurationChange = do
     testAmuletRulesConfigChange : Script ()
     testAmuletRulesConfigChange = do
       (app, dso, (sv1, sv2, sv3, _)) <- initMainNet
    +  let Some fundManager = partyFromText "FundManager"
     
       [(dsoRulesCid, _)] <- query @DsoRules dso
     
    @@ -766,6 +767,7 @@ testAmuletRulesConfigChange = do
             amuletToIssuePerYear = 40e9
             validatorRewardPercentage = 0.05
             appRewardPercentage = 0.15
    +        optDevelopmentFundPercentage = Some 0.05
     
       let defaultIssuanceCurve2 = Schedule with
             initialValue = issuanceConfig_0p5_1p5_2
    @@ -778,6 +780,7 @@ testAmuletRulesConfigChange = do
             transferConfig = defaultTransferConfig2
             issuanceCurve = defaultIssuanceCurve2
             tickDuration = seconds 200
    +        optDevelopmentFundManager = Some fundManager
     
       -- second config that changes the other half of the parameters
       let newConfig2 = amuletRules.configSchedule.initialValue with
    diff --git a/daml/splice-dso-governance/daml.yaml b/daml/splice-dso-governance/daml.yaml
    index ee90977197..f8e314e1c9 100644
    --- a/daml/splice-dso-governance/daml.yaml
    +++ b/daml/splice-dso-governance/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-dso-governance
     source: daml
    -version: 0.1.20
    +version: 0.1.21
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml
    index 71990a32ea..207923b9f7 100644
    --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml
    +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml
    @@ -253,6 +253,12 @@ data DsoRules_ClaimExpiredRewardsResult = DsoRules_ClaimExpiredRewardsResult wit
     data DsoRules_MergeUnclaimedRewardsResult = DsoRules_MergeUnclaimedRewardsResult with
         unclaimedReward: ContractId UnclaimedReward
     
    +data DsoRules_MergeUnclaimedDevelopmentFundCouponsResult = DsoRules_MergeUnclaimedDevelopmentFundCouponsResult with
    +    result : AmuletRules_MergeUnclaimedDevelopmentFundCouponsResult
    +
    +data DsoRules_ExpireDevelopmentFundCouponResult = DsoRules_ExpireDevelopmentFundCouponResult with
    +    result : DevelopmentFundCoupon_DsoExpireResult
    +
     data DsoRules_MiningRound_CloseResult = DsoRules_MiningRound_CloseResult with
         closedRound : ContractId ClosedMiningRound
     
    @@ -1318,6 +1324,29 @@ template DsoRules with
             return DsoRules_MergeUnclaimedRewardsResult with
               unclaimedReward = result.unclaimedRewardCid
     
    +    nonconsuming choice DsoRules_MergeUnclaimedDevelopmentFundCoupons : DsoRules_MergeUnclaimedDevelopmentFundCouponsResult
    +      -- ^ Batch merge of of development fund coupons.
    +      with
    +        amuletRulesCid : ContractId AmuletRules
    +        choiceArg : AmuletRules_MergeUnclaimedDevelopmentFundCoupons
    +        sv : Party
    +      controller sv
    +      do
    +        _ <- getAndValidateSvParty this (Some sv)
    +        result <- exercise amuletRulesCid choiceArg
    +        return DsoRules_MergeUnclaimedDevelopmentFundCouponsResult with result
    +
    +    nonconsuming choice DsoRules_ExpireDevelopmentFundCoupon : DsoRules_ExpireDevelopmentFundCouponResult
    +      -- ^ Expires a DevelopmentFundCoupon and produces an UnclaimedDevelopmentFundCoupon with the same amount.
    +      with
    +        developmentFundCouponCid : ContractId DevelopmentFundCoupon
    +        sv : Party
    +      controller sv
    +      do
    +        _ <- getAndValidateSvParty this (Some sv)
    +        result <- exercise developmentFundCouponCid DevelopmentFundCoupon_DsoExpire
    +        pure $ DsoRules_ExpireDevelopmentFundCouponResult with result
    +
         nonconsuming choice DsoRules_MiningRound_Close : DsoRules_MiningRound_CloseResult
           with
             amuletRulesCid : ContractId AmuletRules
    diff --git a/daml/splice-util-featured-app-proxies-test/daml.yaml b/daml/splice-util-featured-app-proxies-test/daml.yaml
    index 63dbbf1d78..26e7d2b487 100644
    --- a/daml/splice-util-featured-app-proxies-test/daml.yaml
    +++ b/daml/splice-util-featured-app-proxies-test/daml.yaml
    @@ -11,7 +11,7 @@ description: |
       are normal .dar files and can be shared by copying the .dars.
       (TODO(#594): remove this limitation)
     source: daml
    -version: 1.0.6
    +version: 1.0.7
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-util-token-standard-wallet-test/daml.yaml b/daml/splice-util-token-standard-wallet-test/daml.yaml
    index f48fa48e42..2570b91a1b 100644
    --- a/daml/splice-util-token-standard-wallet-test/daml.yaml
    +++ b/daml/splice-util-token-standard-wallet-test/daml.yaml
    @@ -4,7 +4,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-util-token-standard-wallet-test
     source: daml
    -version: 1.0.1
    +version: 1.0.2
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-util/daml.yaml b/daml/splice-util/daml.yaml
    index a65870adc0..49c43d09f9 100644
    --- a/daml/splice-util/daml.yaml
    +++ b/daml/splice-util/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-util
     source: daml
    -version: 0.1.4
    +version: 0.1.5
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-util/daml/Splice/Util.daml b/daml/splice-util/daml/Splice/Util.daml
    index 80ee7987cb..aa9a17a0cd 100644
    --- a/daml/splice-util/daml/Splice/Util.daml
    +++ b/daml/splice-util/daml/Splice/Util.daml
    @@ -206,6 +206,9 @@ instance Patchable RelTime where
     instance Patchable Time where
       patch = patchScalar
     
    +instance Patchable Party where
    +  patch = patchScalar
    +
     mapDifference : Ord k => Map k a -> Map k a -> Map k k
     mapDifference = Map.merge (\_ _ -> None) (\k _ -> Some k) (\_ _ _ -> None)
     
    diff --git a/daml/splice-validator-lifecycle-test/daml.yaml b/daml/splice-validator-lifecycle-test/daml.yaml
    index c4b607ca5c..a4287301a2 100644
    --- a/daml/splice-validator-lifecycle-test/daml.yaml
    +++ b/daml/splice-validator-lifecycle-test/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-validator-lifecycle-test
     source: daml
    -version: 0.1.5
    +version: 0.1.6
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-validator-lifecycle/daml.yaml b/daml/splice-validator-lifecycle/daml.yaml
    index a5193b1b7d..de03e7f857 100644
    --- a/daml/splice-validator-lifecycle/daml.yaml
    +++ b/daml/splice-validator-lifecycle/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-validator-lifecycle
     source: daml
    -version: 0.1.5
    +version: 0.1.6
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-wallet-payments/daml.yaml b/daml/splice-wallet-payments/daml.yaml
    index ba6e3a65b0..3c101ac4a2 100644
    --- a/daml/splice-wallet-payments/daml.yaml
    +++ b/daml/splice-wallet-payments/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-wallet-payments
     source: daml
    -version: 0.1.14
    +version: 0.1.15
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-wallet-test/daml.yaml b/daml/splice-wallet-test/daml.yaml
    index 4426c9813a..5e65c83e29 100644
    --- a/daml/splice-wallet-test/daml.yaml
    +++ b/daml/splice-wallet-test/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-wallet-test
     source: daml
    -version: 0.1.17
    +version: 0.1.18
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splice-wallet/daml.yaml b/daml/splice-wallet/daml.yaml
    index 77310ac0a6..cf1971b83f 100644
    --- a/daml/splice-wallet/daml.yaml
    +++ b/daml/splice-wallet/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splice-wallet
     source: daml
    -version: 0.1.14
    +version: 0.1.15
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splitwell-test/daml.yaml b/daml/splitwell-test/daml.yaml
    index dde8ced5ea..ce9caec535 100644
    --- a/daml/splitwell-test/daml.yaml
    +++ b/daml/splitwell-test/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splitwell-test
     source: daml
    -version: 0.1.17
    +version: 0.1.18
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/daml/splitwell/daml.yaml b/daml/splitwell/daml.yaml
    index e1e7d4f729..762e112666 100644
    --- a/daml/splitwell/daml.yaml
    +++ b/daml/splitwell/daml.yaml
    @@ -1,7 +1,7 @@
     sdk-version: 3.3.0-snapshot.20250502.13767.0.v2fc6c7e2
     name: splitwell
     source: daml
    -version: 0.1.14
    +version: 0.1.15
     dependencies:
       - daml-prim
       - daml-stdlib
    diff --git a/docs/src/release_notes.rst b/docs/src/release_notes.rst
    index 498b23db4b..811f412128 100644
    --- a/docs/src/release_notes.rst
    +++ b/docs/src/release_notes.rst
    @@ -9,7 +9,97 @@
     
     .. release-notes:: 0.5.6
     
    - - Sequencer
    +  .. important::
    +
    +      **Action recommended from app devs:**
    +
    +      **App devs whose app's Daml code statically depends on** ``splice-amulet < 0.1.15`` should recompile their Daml code
    +      to link against ``splice-amulet >= 0.1.15`` in order to be ready to consume the two new fields introduced in `AmuletConfig`
    +      (`optDevelopmentFundManager`) and `IssuanceConfig` (`optDevelopmentFundPercentage`) once either of them is set.
    +
    +      This is required because once the new fields are set, downgrades of `AmuletRules` will fail.
    +      At the moment, this recompilation is not strictly required, as setting these fields is not planned immediately.
    +
    +      No change is required for apps that build against the :ref:`token_standard`
    +      or :ref:`featured_app_activity_markers_api`.
    +
    +  - Daml
    +
    +    - Implement Daml changes for `CIP-0082 - Establish a 5% Development Fund (Foundation-Governed) `__:
    +
    +      - New templates:
    +
    +        - **UnclaimedDevelopmentFundCoupon**: Represents unallocated Development Fund entitlements created per issuance round.
    +          Coupons are owned by the DSO, have no expiry, and serve as accounting instruments.
    +          ACS size is managed through merging rather than expiration.
    +
    +        - **DevelopmentFundCoupon**: Represents an allocated portion of the Development Fund for a specific beneficiary.
    +          Coupons can be withdrawn by the Development Fund Manager or expired by the DSO, in both cases restoring the
    +          amount to an unclaimed coupon.
    +
    +      - Configuration extensions:
    +
    +        - ``IssuanceConfig`` is extended with an optional ``optDevelopmentFundPercentage``, defining the fraction of each
    +          mint allocated to the Development Fund (validated to be within ``[0.0, 1.0]``).
    +
    +        - ``AmuletConfig`` is extended with an optional ``optDevelopmentFundManager``, designating the party authorized
    +          to allocate Development Fund entitlements.
    +
    +      - AmuletRules updates:
    +
    +        - Modify ``AmuletRules_MiningRound_StartIssuing``: Issuance logic now deducts the Development Fund share
    +          before distributing rewards. When a nonzero allocation is configured, a new
    +          ``UnclaimedDevelopmentFundCoupon`` is created per round. If ``optDevelopmentFundPercentage`` is ``None``,
    +          a default value of **0.05** is applied.
    +          The accrual of ``UnclaimedDevelopmentFundCoupon`` contracts thus starts
    +          as soon as the new Daml models are voted in.
    +
    +        - A new choice ``AmuletRules_MergeUnclaimedDevelopmentFundCoupons``: Adds a batch merge operation to combine
    +          multiple ``UnclaimedDevelopmentFundCoupon`` contracts into a single one for ACS size control.
    +
    +        - A new choice ``AmuletRules_AllocateDevelopmentFundCoupon``: Allows the Development Fund Manager to allocate
    +          unclaimed entitlements to beneficiaries, creating ``DevelopmentFundCoupon`` contracts and returning any
    +          remaining unclaimed amount.
    +
    +        - Modify ``AmuletRules_Transfer``: Transfers now accept ``DevelopmentFundCoupon`` as a valid input when
    +          the sender matches the beneficiary and report the total Development Fund amount consumed.
    +
    +      - DsoRules updates:
    +
    +        - A a new choice ``DsoRules_MergeUnclaimedDevelopmentFundCoupons``: Enables the DSO to trigger unclaimed coupon
    +          merges via governance.
    +
    +        - Add a new ``DsoRules_ExpireDevelopmentFundCoupon``: Allows the DSO to expire an allocated
    +          ``DevelopmentFundCoupon``, restoring its amount to an ``UnclaimedDevelopmentFundCoupon``.
    +
    +      Note that the UI changes in the Wallet app required to allocate funds are not yet implemented and will be delivered in a later release. Please refer to
    +      this issue: `Tracking - CIP-0082 - 5% Development Fund `.
    +
    +      These Daml changes require an upgrade to the following Daml versions **before**
    +      voting to set the transfer fees to zero:
    +
    +      ================== =======
    +      name               version
    +      ================== =======
    +      amulet             0.1.15
    +      amuletNameService  0.1.16
    +      dsoGovernance      0.1.21
    +      splitwell          0.1.15
    +      validatorLifecycle 0.1.6
    +      wallet             0.1.15
    +      walletPayments     0.1.15
    +      ================== =======
    +
    +  - SV app
    +
    +    - Add a new trigger, `MergeUnclaimedDevelopmentFundCouponsTrigger`` that automatically monitors ``UnclaimedDevelopmentFundCoupon`` and,
    +      once their number reaches at least twice the configured threshold, merges the smallest coupons into a single one.
    +      This approach keeps contract-ids of larger coupons stable to minimize contention with externally prepared transactions which reference these ids.
    +
    +    - Add a new config field to ``SvOnboardingConfig`` named ``unclaimedDevelopmentFundCouponsThreshold`` defining the
    +      threshold above which ``UnclaimedDevelopmentFundCoupon`` s are merged. The default value is set to 10.
    +
    +  - Sequencer
     
         - Includes a number of performance improvements that should improve the stability of the sequencer under higher load.
     
    diff --git a/scripts/scan-txlog/scan_txlog.py b/scripts/scan-txlog/scan_txlog.py
    index 3cd836e406..cca4c243bb 100755
    --- a/scripts/scan-txlog/scan_txlog.py
    +++ b/scripts/scan-txlog/scan_txlog.py
    @@ -3909,6 +3909,8 @@ def handle_root_exercised_event(self, transaction, event):
                     return HandleTransactionResult.empty()
                 case "DsoRules_MergeValidatorLicense":
                     return HandleTransactionResult.empty()
    +            case "DsoRules_MergeUnclaimedDevelopmentFundCoupons":
    +                return HandleTransactionResult.empty()
                 case "ExternalPartyAmuletRules_CreateTransferCommand":
                     return HandleTransactionResult.empty()
                 case "FeaturedAppRight_CreateActivityMarker":
    diff --git a/test-full-class-names.log b/test-full-class-names.log
    index 68442c0270..2179905b69 100644
    --- a/test-full-class-names.log
    +++ b/test-full-class-names.log
    @@ -9,6 +9,7 @@ org.lfdecentralizedtrust.splice.integration.tests.BootstrapPackageConfigDarUploa
     org.lfdecentralizedtrust.splice.integration.tests.BootstrapTest
     org.lfdecentralizedtrust.splice.integration.tests.CombinedDumpDirectoryExportIntegrationTest
     org.lfdecentralizedtrust.splice.integration.tests.ConfigurationProvidedBftScanConnectionIntegrationTest
    +org.lfdecentralizedtrust.splice.integration.tests.DevelopmentFundCouponIntegrationTest
     org.lfdecentralizedtrust.splice.integration.tests.DirectoryPeriodicBackupIntegrationTest
     org.lfdecentralizedtrust.splice.integration.tests.DistributedDomainIntegrationTest
     org.lfdecentralizedtrust.splice.integration.tests.ExternalPartySetupProposalIntegrationTest
    diff --git a/token-standard/splice-token-standard-test/daml.yaml b/token-standard/splice-token-standard-test/daml.yaml
    index 2cc5e6b52a..13765be412 100644
    --- a/token-standard/splice-token-standard-test/daml.yaml
    +++ b/token-standard/splice-token-standard-test/daml.yaml
    @@ -16,7 +16,7 @@ description: |
       as Daml script code can currently not be shared via .dars across SDKs. The dependencies
       are normal .dar files and can be shared by copying the .dars.
       (TODO(#594): remove this limitation)
    -version: 1.0.8
    +version: 1.0.9
     source: daml
     dependencies:
       - daml-prim
    diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml
    index a806f1c567..f70b864461 100644
    --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml
    +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml
    @@ -88,6 +88,8 @@ defaultAmuletConfig = AmuletConfig with
       -- Amount of the AppRewardCoupon contract that a FeaturedAppActivityMarker is converted to.
       featuredAppActivityMarkerAmount = Some defaultFeaturedAppActivityMarkerAmount
     
    +  optDevelopmentFundManager = None
    +
     -- | Default configuration schedule with single current amulet config
     defaultAmuletConfigSchedule : Schedule Time (AmuletConfig USD)
     defaultAmuletConfigSchedule = Schedule with
    @@ -131,6 +133,7 @@ issuanceConfig_10plus = IssuanceConfig with
       featuredAppRewardCap = 100.0
       unfeaturedAppRewardCap = 0.6
       optValidatorFaucetCap = None -- We use the default of 2.85 USD introduced in the upgrade for CIP-3
    +  optDevelopmentFundPercentage = Some 0.0
     
     
     defaultIssuanceCurve : Schedule RelTime IssuanceConfig