From c4ffbfbf9c54881e3fb9a80538ddbb81b9101c4a Mon Sep 17 00:00:00 2001
From: Michael Fletcher <36506122+Fletch153@users.noreply.github.com>
Date: Wed, 31 Jul 2024 12:33:33 +0100
Subject: [PATCH] Initial Impl of SBRV (#13813)

* Initial Impl of SBRV

* Generate wrappers/ABI

* Removed ZeroAddress check for feemanager & AC

* Update gethwrappers

* Rename RewardManager=>DestinationRewardManager

* Remove circular dependency between proxy and verifier

* Fixes to init logic

* Update gethwrappers

* llo-feeds: v0.4.0 reward manager tests

* Add check to remove assumption that feeManager cannot be nil

* Generate

* Fixed poolIdMismatch not being thrown

* Fix logic error when looking up the activeDonConfig

* Update gethwrappers

* Update interface sanity checks when setting verifier

* Add remaining interface functionality

* Update gethwrappers

* llo-feeds: verifier SetConfig tests

* llo-feeds: adding verifier contract get methods for easier testing

* adding rewards wire up for testing

* incomeplte fix for test_setConfigWithAddressesAndWeightsAreSetCorrectly

* llo-feeds: adjusting setConfig tests due to changes

* llo-feeds: fee manager v0.4.0 tests: making v0.3.0 tests pass

* llo-feeds: fee manager v0.4.0 tests: fee manager - adding test for PoolIdMismatch

* llo-feeds: fee manager v0.4.0 tests: nits

* llo-feeds: feeManager bulk reverts when PoolId is 0

* llo-feeds: feeManager tests revertOnSettingAnAddressZeroVerifier onlyCallableByOwnerReverts

* llo-feeds: feeManager test poolIdsCannotBeZeroAddress

* llo-feeds: rewardManager test_rewardsAreCorrectlySentToEachAssociatedPoolWhenVerifyingInBulk

* llo-feeds: verifier proxy tests

* llo-feeds: fixing DestinationProxy error handling

* llo-feeds: fixing DestinationProxy test remove verifiercontract zero check

* llo-feeds: remove interface checks for verifierProxy

* llo-feeds: clean up setConfig tests from checking internal state

* fix issue when processing rewards

* Apply suggestions from code review

* Update contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol

* Update contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol

* llo-feeds: verifier proxy remove test test_setVerifierZeroVerifier

* llo-feeds: tests for verify and verifyBulk

* clean up

* temporal fix for compile error

* llo: v0.4.0 removing V1 report tests

* better comments

* llo-feeds: verify test test_rollingOutConfiguration

* llo-feeds: verify test test_verifyFailsWhenReportIsOlderThanConfig

* llo-feeds: verify test test_verifyFailsWhenReportIsOlderThanConfig

* llo-feeds: tests for billing / billing bulk

* fixing tests

* fixing tests names

* llo-feeds: fix proxy contract should send value in call to verify

* llo-feeds: fix billing tests

* squash me

* llo-feeds: billing bulk verify tests

* llo-feeds: clean up

* llo-feeds: VerifierSetAccessControllerTest

* llo-feeds: clean up

* llo-feeds: extra proxy tests

* Update contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol

* Fix issue with oldest config verifying incorrectly

* Fix issue with oldest config verifying incorrectly

* llo-feeds: fix underflow and test_verifyFailsWhenReportIsOlderThanConfig

* Update gethwrappers

* Update gethwrappers

* llo-feeds: DestinationVerifier setFeeManager tests

* llo-feeds: Tests for Rewards and configs

* llo-feeds: Tests for DestinationVerifier constructor

* llo-feeds: reverse looping efficiently

* Update contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol

* Update gethwrappers

* llo-feeds: fmt contract tests

* llo-feeds: npx prettify contract tests

* llo-feeds: npx prettify v0.4.0 contracts

* se --Add ability to amend config array

* Update gethwrappers

* Fix issue with future timestamps

* Update gethwrappers

* Small fix

* Fix broken tests

* Update gethwrappers

* llo-feeds: Tests setConfigWithActivationTime

* llo-feeds: Tests setConfigWithActivationTime

* llo-feeds: Tests VerifierRemoveLatestConfigTest

* llo-feeds: fixing linter errors (unused imports)

* llo-feeds: tests better filenaming

* Improve upgradability of contracts

* Update gethwrappers

* Small fix when setting rewardManager

* Update gethwrappers

* Fix var name to honour same interface

* Update gethwrappers

* Improve getter consistency

* Update gethwrappers

* Fix comments & conventions

* Update DON Config to camal case

* Update gethwrappers

* solhint + gas snapshot

* prettier

* Solhint fixes

* Update gethwrappers

* gas snapshot

* Fixed gas issue

* Gas snapshot

* llo-feeds: testing multiple fee managers and verifiers

* llo: fixing tests

* Update contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol

Co-authored-by: msuchacz-cll <170782674+msuchacz-cll@users.noreply.github.com>

* llo: v0.4.0 interfaces test

* Fixed gas snaposhot

* prettier

* Generate

---------

Co-authored-by: Sam Davies <samsondav@protonmail.com>
Co-authored-by: app-token-issuer-infra-releng[bot] <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com>
Co-authored-by: David Przybilla <dav.alejandro@gmail.com>
Co-authored-by: ad0ll <20057155+ad0ll@users.noreply.github.com>
Co-authored-by: msuchacz-cll <170782674+msuchacz-cll@users.noreply.github.com>
---
 .../gas-snapshots/llo-feeds.gas-snapshot      |  241 +++
 .../scripts/native_solc_compile_all_llo-feeds |   16 +-
 .../src/v0.8/llo-feeds/libraries/Common.sol   |   46 +
 .../{ => libraries}/test/ByteUtilTest.t.sol   |    2 +-
 .../llo-feeds/{ => v0.3.0}/FeeManager.sol     |   16 +-
 .../llo-feeds/{ => v0.3.0}/RewardManager.sol  |   10 +-
 .../v0.8/llo-feeds/{ => v0.3.0}/Verifier.sol  |    8 +-
 .../llo-feeds/{ => v0.3.0}/VerifierProxy.sol  |   10 +-
 .../{ => v0.3.0}/interfaces/IFeeManager.sol   |    4 +-
 .../interfaces/IRewardManager.sol             |    4 +-
 .../{ => v0.3.0}/interfaces/IVerifier.sol     |    4 +-
 .../interfaces/IVerifierFeeManager.sol        |    4 +-
 .../interfaces/IVerifierProxy.sol             |    4 +-
 .../test/fee-manager/BaseFeeManager.t.sol     |    6 +-
 .../test/fee-manager/FeeManager.general.t.sol |    0
 .../FeeManager.getFeeAndReward.t.sol          |   12 +-
 .../fee-manager/FeeManager.processFee.t.sol   |    2 +-
 .../FeeManager.processFeeBulk.t.sol           |    0
 .../test/gas/Gas_VerifierTest.t.sol           |    4 +-
 .../test/mocks/ErroredVerifier.sol            |   31 +-
 .../test/mocks/ExposedVerifier.sol            |    0
 .../test/mocks/FeeManagerProxy.sol            |   10 +-
 .../reward-manager/BaseRewardManager.t.sol    |    6 +-
 .../reward-manager/RewardManager.claim.t.sol  |    4 +-
 .../RewardManager.general.t.sol               |    4 +-
 .../RewardManager.payRecipients.t.sol         |    0
 .../RewardManager.setRecipients.t.sol         |    2 +-
 ...RewardManager.updateRewardRecipients.t.sol |    2 +-
 .../test/verifier/BaseVerifierTest.t.sol      |   11 +-
 .../verifier/VerifierActivateConfigTest.t.sol |    2 +-
 .../verifier/VerifierDeactivateFeedTest.t.sol |    2 +-
 .../VerifierProxyConstructorTest.t.sol        |    6 +-
 .../VerifierProxyInitializeVerifierTest.t.sol |    2 +-
 ...VerifierProxySetAccessControllerTest.t.sol |    2 +-
 .../VerifierProxySetVerifierTest.t.sol        |    6 +-
 .../test/verifier/VerifierProxyTest.t.sol     |    4 +-
 .../VerifierProxyUnsetVerifierTest.t.sol      |    2 +-
 .../VerifierSetConfigFromSourceTest.t.sol     |    2 +-
 .../test/verifier/VerifierSetConfigTest.t.sol |    4 +-
 .../test/verifier/VerifierTest.t.sol          |    6 +-
 .../verifier/VerifierTestBillingReport.t.sol  |    0
 .../verifier/VerifierUnsetConfigTest.t.sol    |    2 +-
 .../test/verifier/VerifierVerifyTest.t.sol    |   10 +-
 .../v0.4.0/DestinationFeeManager.sol          |  557 +++++
 .../v0.4.0/DestinationRewardManager.sol       |  336 ++++
 .../llo-feeds/v0.4.0/DestinationVerifier.sol  |  434 ++++
 .../v0.4.0/DestinationVerifierProxy.sol       |   79 +
 .../interfaces/IDestinationFeeManager.sol     |  133 ++
 .../interfaces/IDestinationRewardManager.sol  |   75 +
 .../interfaces/IDestinationVerifier.sol       |   98 +
 .../interfaces/IDestinationVerifierProxy.sol  |   50 +
 .../BaseDestinationFeeManager.t.sol           |  394 ++++
 .../DestinationFeeManager.general.t.sol       |  299 +++
 ...estinationFeeManager.getFeeAndReward.t.sol |  606 ++++++
 .../DestinationFeeManager.processFee.t.sol    |  492 +++++
 ...DestinationFeeManager.processFeeBulk.t.sol |  310 +++
 .../test/mocks/DestinationFeeManagerProxy.sol |   24 +
 .../BaseDestinationRewardManager.t.sol        |  264 +++
 .../DestinationRewardManager.claim.t.sol      |  790 ++++++++
 .../DestinationRewardManager.general.t.sol    |   99 +
 ...stinationRewardManager.payRecipients.t.sol |  194 ++
 ...stinationRewardManager.setRecipients.t.sol |  148 ++
 ...RewardManager.updateRewardRecipients.t.sol |  450 +++++
 .../BaseDestinationVerifierTest.t.sol         |  347 ++++
 .../DestinationVerifierInterfacesTest.t.sol   |  128 ++
 .../DestinationVerifierProxyTest.t.sol        |   38 +
 ...nationVerifierRemoveLatestConfigTest.t.sol |  126 ++
 ...ationVerifierSetAccessControllerTest.t.sol |   33 +
 .../DestinationVerifierSetConfigTest.t.sol    |  159 ++
 ...DestinationVerifierSetFeeManagerTest.t.sol |   28 +
 .../verifier/DestinationVerifierTest.t.sol    |   31 +
 ...DestinationVerifierTestBillingReport.t.sol |  189 ++
 .../DestinationVerifierTestRewards.t.sol      |  227 +++
 ...erTestRewardsMultiVefifierFeeManager.t.sol |  141 ++
 .../DestinationVerifierVerifyBulkTest.t.sol   |  140 ++
 .../DestinationVerifierVerifyTest.t.sol       |  711 +++++++
 .../destination_fee_manager.go                | 1790 +++++++++++++++++
 .../destination_reward_manager.go             | 1434 +++++++++++++
 .../destination_verifier.go                   | 1576 +++++++++++++++
 .../destination_verifier_proxy.go             |  676 +++++++
 .../errored_verifier/errored_verifier.go      |    4 +-
 ...rapper-dependency-versions-do-not-edit.txt |    6 +-
 core/gethwrappers/llo-feeds/go_generate.go    |    5 +
 83 files changed, 14025 insertions(+), 109 deletions(-)
 rename contracts/src/v0.8/llo-feeds/{ => libraries}/test/ByteUtilTest.t.sol (99%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/FeeManager.sol (96%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/RewardManager.sol (96%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/Verifier.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/VerifierProxy.sol (95%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/interfaces/IFeeManager.sol (94%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/interfaces/IRewardManager.sol (94%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/interfaces/IVerifier.sol (97%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/interfaces/IVerifierFeeManager.sol (88%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/interfaces/IVerifierProxy.sol (95%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/fee-manager/BaseFeeManager.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/fee-manager/FeeManager.general.t.sol (100%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/fee-manager/FeeManager.getFeeAndReward.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/fee-manager/FeeManager.processFee.t.sol (99%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/fee-manager/FeeManager.processFeeBulk.t.sol (100%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/gas/Gas_VerifierTest.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/mocks/ErroredVerifier.sol (66%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/mocks/ExposedVerifier.sol (100%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/mocks/FeeManagerProxy.sol (61%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/BaseRewardManager.t.sol (97%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/RewardManager.claim.t.sol (99%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/RewardManager.general.t.sol (92%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/RewardManager.payRecipients.t.sol (100%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/RewardManager.setRecipients.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/reward-manager/RewardManager.updateRewardRecipients.t.sol (99%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/BaseVerifierTest.t.sol (97%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierActivateConfigTest.t.sol (97%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierDeactivateFeedTest.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxyConstructorTest.t.sol (77%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxyInitializeVerifierTest.t.sol (93%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxySetAccessControllerTest.t.sol (92%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxySetVerifierTest.t.sol (88%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxyTest.t.sol (87%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierProxyUnsetVerifierTest.t.sol (95%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierSetConfigFromSourceTest.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierSetConfigTest.t.sol (98%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierTest.t.sol (88%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierTestBillingReport.t.sol (100%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierUnsetConfigTest.t.sol (97%)
 rename contracts/src/v0.8/llo-feeds/{ => v0.3.0}/test/verifier/VerifierVerifyTest.t.sol (97%)
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol
 create mode 100644 contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol
 create mode 100644 core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go
 create mode 100644 core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go
 create mode 100644 core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go
 create mode 100644 core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go

diff --git a/contracts/gas-snapshots/llo-feeds.gas-snapshot b/contracts/gas-snapshots/llo-feeds.gas-snapshot
index 0162809e90d..89073a78468 100644
--- a/contracts/gas-snapshots/llo-feeds.gas-snapshot
+++ b/contracts/gas-snapshots/llo-feeds.gas-snapshot
@@ -18,6 +18,220 @@ ByteUtilTest:test_readUint32MultiWord() (gas: 3393)
 ByteUtilTest:test_readUint32WithEmptyArray() (gas: 3253)
 ByteUtilTest:test_readUint32WithNotEnoughBytes() (gas: 3272)
 ByteUtilTest:test_readZeroAddress() (gas: 3365)
+DestinationFeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52669)
+DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52685)
+DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78876)
+DestinationFeeManagerProcessFeeTest:test_V1PayloadVerifies() (gas: 29324)
+DestinationFeeManagerProcessFeeTest:test_V1PayloadVerifiesAndReturnsChange() (gas: 61187)
+DestinationFeeManagerProcessFeeTest:test_V2PayloadVerifies() (gas: 121137)
+DestinationFeeManagerProcessFeeTest:test_V2PayloadWithoutQuoteFails() (gas: 29669)
+DestinationFeeManagerProcessFeeTest:test_V2PayloadWithoutZeroFee() (gas: 74797)
+DestinationFeeManagerProcessFeeTest:test_WithdrawERC20() (gas: 72796)
+DestinationFeeManagerProcessFeeTest:test_WithdrawNonAdminAddr() (gas: 56334)
+DestinationFeeManagerProcessFeeTest:test_WithdrawUnwrappedNative() (gas: 26411)
+DestinationFeeManagerProcessFeeTest:test_addVerifier() (gas: 131079)
+DestinationFeeManagerProcessFeeTest:test_addVerifierExistingAddress() (gas: 34148)
+DestinationFeeManagerProcessFeeTest:test_baseFeeIsAppliedForLink() (gas: 17214)
+DestinationFeeManagerProcessFeeTest:test_baseFeeIsAppliedForNative() (gas: 20152)
+DestinationFeeManagerProcessFeeTest:test_correctDiscountIsAppliedWhenBothTokensAreDiscounted() (gas: 91103)
+DestinationFeeManagerProcessFeeTest:test_discountAIsNotAppliedWhenSetForOtherUsers() (gas: 56580)
+DestinationFeeManagerProcessFeeTest:test_discountFeeRoundsDownWhenUneven() (gas: 52871)
+DestinationFeeManagerProcessFeeTest:test_discountIsAppliedForLink() (gas: 49682)
+DestinationFeeManagerProcessFeeTest:test_discountIsAppliedWith100PercentSurcharge() (gas: 78949)
+DestinationFeeManagerProcessFeeTest:test_discountIsNoLongerAppliedAfterRemoving() (gas: 46567)
+DestinationFeeManagerProcessFeeTest:test_discountIsNotAppliedForInvalidTokenAddress() (gas: 17582)
+DestinationFeeManagerProcessFeeTest:test_discountIsNotAppliedToOtherFeeds() (gas: 54628)
+DestinationFeeManagerProcessFeeTest:test_discountIsReturnedForLink() (gas: 49654)
+DestinationFeeManagerProcessFeeTest:test_emptyQuoteRevertsWithError() (gas: 12231)
+DestinationFeeManagerProcessFeeTest:test_eventIsEmittedAfterSurchargeIsSet() (gas: 41424)
+DestinationFeeManagerProcessFeeTest:test_eventIsEmittedIfNotEnoughLink() (gas: 179229)
+DestinationFeeManagerProcessFeeTest:test_eventIsEmittedUponWithdraw() (gas: 69057)
+DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterDiscountIsRemoved() (gas: 49831)
+DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterNewDiscountIsApplied() (gas: 67769)
+DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterNewSurchargeIsApplied() (gas: 64460)
+DestinationFeeManagerProcessFeeTest:test_feeIsZeroWith100PercentDiscount() (gas: 52091)
+DestinationFeeManagerProcessFeeTest:test_getBaseRewardWithLinkQuote() (gas: 17231)
+DestinationFeeManagerProcessFeeTest:test_getLinkFeeIsRoundedUp() (gas: 49853)
+DestinationFeeManagerProcessFeeTest:test_getLinkRewardIsSameAsFee() (gas: 55711)
+DestinationFeeManagerProcessFeeTest:test_getLinkRewardWithNativeQuoteAndSurchargeWithLinkDiscount() (gas: 82833)
+DestinationFeeManagerProcessFeeTest:test_getRewardWithLinkDiscount() (gas: 49700)
+DestinationFeeManagerProcessFeeTest:test_getRewardWithLinkQuoteAndLinkDiscount() (gas: 49681)
+DestinationFeeManagerProcessFeeTest:test_getRewardWithNativeQuote() (gas: 20150)
+DestinationFeeManagerProcessFeeTest:test_getRewardWithNativeQuoteAndSurcharge() (gas: 50885)
+DestinationFeeManagerProcessFeeTest:test_linkAvailableForPaymentReturnsLinkBalance() (gas: 53172)
+DestinationFeeManagerProcessFeeTest:test_nativeSurcharge0Percent() (gas: 30937)
+DestinationFeeManagerProcessFeeTest:test_nativeSurcharge100Percent() (gas: 50887)
+DestinationFeeManagerProcessFeeTest:test_nativeSurchargeCannotExceed100Percent() (gas: 17220)
+DestinationFeeManagerProcessFeeTest:test_nativeSurchargeEventIsEmittedOnUpdate() (gas: 41402)
+DestinationFeeManagerProcessFeeTest:test_noFeeIsAppliedWhenReportHasZeroFee() (gas: 51914)
+DestinationFeeManagerProcessFeeTest:test_noFeeIsAppliedWhenReportHasZeroFeeAndDiscountAndSurchargeIsSet() (gas: 78172)
+DestinationFeeManagerProcessFeeTest:test_nonAdminProxyUserCannotProcessFee() (gas: 24185)
+DestinationFeeManagerProcessFeeTest:test_nonAdminUserCanNotSetDiscount() (gas: 19871)
+DestinationFeeManagerProcessFeeTest:test_onlyCallableByOwnerReverts() (gas: 15453)
+DestinationFeeManagerProcessFeeTest:test_payLinkDeficit() (gas: 198072)
+DestinationFeeManagerProcessFeeTest:test_payLinkDeficitOnlyCallableByAdmin() (gas: 17415)
+DestinationFeeManagerProcessFeeTest:test_payLinkDeficitPaysAllFeesProcessed() (gas: 218691)
+DestinationFeeManagerProcessFeeTest:test_payLinkDeficitTwice() (gas: 202446)
+DestinationFeeManagerProcessFeeTest:test_poolIdsCannotBeZeroAddress() (gas: 115317)
+DestinationFeeManagerProcessFeeTest:test_processFeeAsProxy() (gas: 121475)
+DestinationFeeManagerProcessFeeTest:test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() (gas: 29745)
+DestinationFeeManagerProcessFeeTest:test_processFeeEmitsEventIfNotEnoughLink() (gas: 165393)
+DestinationFeeManagerProcessFeeTest:test_processFeeIfSubscriberIsSelf() (gas: 30063)
+DestinationFeeManagerProcessFeeTest:test_processFeeNative() (gas: 178204)
+DestinationFeeManagerProcessFeeTest:test_processFeeUsesCorrectDigest() (gas: 122766)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() (gas: 31822)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithDiscountEmitsEvent() (gas: 245890)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithInvalidReportVersionFailsToDecode() (gas: 30770)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithNoDiscountDoesNotEmitEvent() (gas: 171109)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNative() (gas: 186069)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeLinkAddress() (gas: 135874)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() (gas: 161459)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeShortFunds() (gas: 94841)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeWithExcessiveFee() (gas: 193032)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithWithCorruptQuotePayload() (gas: 75084)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithWithEmptyQuotePayload() (gas: 30006)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithWithZeroQuotePayload() (gas: 30056)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() (gas: 35320)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() (gas: 158081)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkReturnsChange() (gas: 56059)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() (gas: 121473)
+DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() (gas: 38024)
+DestinationFeeManagerProcessFeeTest:test_processMultipleLinkReports() (gas: 230434)
+DestinationFeeManagerProcessFeeTest:test_processMultipleUnwrappedNativeReports() (gas: 264223)
+DestinationFeeManagerProcessFeeTest:test_processMultipleV1Reports() (gas: 81155)
+DestinationFeeManagerProcessFeeTest:test_processMultipleWrappedNativeReports() (gas: 247072)
+DestinationFeeManagerProcessFeeTest:test_processPoolIdsPassedMismatched() (gas: 98793)
+DestinationFeeManagerProcessFeeTest:test_processV1V2V3Reports() (gas: 215445)
+DestinationFeeManagerProcessFeeTest:test_processV1V2V3ReportsWithUnwrapped() (gas: 257087)
+DestinationFeeManagerProcessFeeTest:test_removeVerifierNonExistentAddress() (gas: 12822)
+DestinationFeeManagerProcessFeeTest:test_removeVerifierZeroAaddress() (gas: 10678)
+DestinationFeeManagerProcessFeeTest:test_reportWithNoExpiryOrFeeReturnsZero() (gas: 13615)
+DestinationFeeManagerProcessFeeTest:test_revertOnSettingAnAddressZeroVerifier() (gas: 10614)
+DestinationFeeManagerProcessFeeTest:test_rewardsAreCorrectlySentToEachAssociatedPoolWhenVerifyingInBulk() (gas: 263181)
+DestinationFeeManagerProcessFeeTest:test_setDiscountOver100Percent() (gas: 19562)
+DestinationFeeManagerProcessFeeTest:test_setRewardManagerZeroAddress() (gas: 10626)
+DestinationFeeManagerProcessFeeTest:test_subscriberDiscountEventIsEmittedOnUpdate() (gas: 46329)
+DestinationFeeManagerProcessFeeTest:test_surchargeFeeRoundsUpWhenUneven() (gas: 51261)
+DestinationFeeManagerProcessFeeTest:test_surchargeIsApplied() (gas: 51165)
+DestinationFeeManagerProcessFeeTest:test_surchargeIsAppliedForNativeFeeWithDiscount() (gas: 79356)
+DestinationFeeManagerProcessFeeTest:test_surchargeIsNoLongerAppliedAfterRemoving() (gas: 47132)
+DestinationFeeManagerProcessFeeTest:test_surchargeIsNotAppliedForLinkFee() (gas: 49962)
+DestinationFeeManagerProcessFeeTest:test_surchargeIsNotAppliedWith100PercentDiscount() (gas: 78352)
+DestinationFeeManagerProcessFeeTest:test_testRevertIfReportHasExpired() (gas: 14943)
+DestinationRewardManagerClaimTest:test_claimAllRecipients() (gas: 277191)
+DestinationRewardManagerClaimTest:test_claimMultipleRecipients() (gas: 154371)
+DestinationRewardManagerClaimTest:test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() (gas: 330208)
+DestinationRewardManagerClaimTest:test_claimSingleRecipient() (gas: 89039)
+DestinationRewardManagerClaimTest:test_claimUnevenAmountRoundsDown() (gas: 315411)
+DestinationRewardManagerClaimTest:test_claimUnregisteredPoolId() (gas: 35164)
+DestinationRewardManagerClaimTest:test_claimUnregisteredRecipient() (gas: 41201)
+DestinationRewardManagerClaimTest:test_eventIsEmittedUponClaim() (gas: 86084)
+DestinationRewardManagerClaimTest:test_eventIsNotEmittedUponUnsuccessfulClaim() (gas: 25050)
+DestinationRewardManagerClaimTest:test_recipientsClaimMultipleDeposits() (gas: 386857)
+DestinationRewardManagerClaimTest:test_singleRecipientClaimMultipleDeposits() (gas: 137777)
+DestinationRewardManagerNoRecipientSet:test_claimAllRecipientsAfterRecipientsSet() (gas: 492227)
+DestinationRewardManagerPayRecipientsTest:test_addFundsToPoolAsNonOwnerOrFeeManager() (gas: 11503)
+DestinationRewardManagerPayRecipientsTest:test_addFundsToPoolAsOwner() (gas: 53944)
+DestinationRewardManagerPayRecipientsTest:test_payAllRecipients() (gas: 250829)
+DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsFromNonAdminUser() (gas: 20496)
+DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsFromRecipientInPool() (gas: 251075)
+DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsWithAdditionalInvalidRecipient() (gas: 262275)
+DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsWithAdditionalUnregisteredRecipient() (gas: 265760)
+DestinationRewardManagerPayRecipientsTest:test_payRecipientWithInvalidPool() (gas: 28908)
+DestinationRewardManagerPayRecipientsTest:test_payRecipientsEmptyRecipientList() (gas: 25333)
+DestinationRewardManagerPayRecipientsTest:test_payRecipientsWithInvalidPoolId() (gas: 31402)
+DestinationRewardManagerPayRecipientsTest:test_paySingleRecipient() (gas: 84709)
+DestinationRewardManagerPayRecipientsTest:test_paySubsetOfRecipientsInPool() (gas: 198474)
+DestinationRewardManagerRecipientClaimDifferentWeightsTest:test_allRecipientsClaimingReceiveExpectedAmount() (gas: 280853)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimAllRecipientsMultiplePools() (gas: 512489)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimAllRecipientsSinglePool() (gas: 283649)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimEmptyPoolWhenSecondPoolContainsFunds() (gas: 293497)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimMultipleRecipientsMultiplePools() (gas: 263075)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimMultipleRecipientsSinglePool() (gas: 154537)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimSingleRecipientMultiplePools() (gas: 132653)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimSingleUniqueRecipient() (gas: 106056)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimUnevenAmountRoundsDown() (gas: 579776)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimUnregisteredRecipient() (gas: 64664)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorAndTotalPoolsEqual() (gas: 13074)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorCannotBeGreaterThanTotalPools() (gas: 12703)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorSingleResult() (gas: 22471)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInBothPools() (gas: 32248)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInBothPoolsWhereAlreadyClaimed() (gas: 148629)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInNoPools() (gas: 21728)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInSinglePool() (gas: 27765)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_recipientsClaimMultipleDeposits() (gas: 391427)
+DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_singleRecipientClaimMultipleDeposits() (gas: 137862)
+DestinationRewardManagerRecipientClaimUnevenWeightTest:test_allRecipientsClaimingReceiveExpectedAmount() (gas: 199546)
+DestinationRewardManagerRecipientClaimUnevenWeightTest:test_allRecipientsClaimingReceiveExpectedAmountWithSmallDeposit() (gas: 219419)
+DestinationRewardManagerSetRecipientsTest:test_eventIsEmittedUponSetRecipients() (gas: 191707)
+DestinationRewardManagerSetRecipientsTest:test_setRecipientContainsDuplicateRecipients() (gas: 126060)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientFromManagerAddress() (gas: 214117)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientFromNonOwnerOrFeeManagerAddress() (gas: 21496)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientTwice() (gas: 193280)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWeights() (gas: 180608)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWithZeroAddress() (gas: 90202)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWithZeroWeight() (gas: 191312)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipients() (gas: 185567)
+DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientsIsEmpty() (gas: 87091)
+DestinationRewardManagerSetRecipientsTest:test_setSingleRewardRecipient() (gas: 110349)
+DestinationRewardManagerSetupTest:test_addFeeManagerExistingAddress() (gas: 35281)
+DestinationRewardManagerSetupTest:test_addFeeManagerZeroAddress() (gas: 10580)
+DestinationRewardManagerSetupTest:test_addRemoveFeeManager() (gas: 48248)
+DestinationRewardManagerSetupTest:test_eventEmittedUponFeeManagerUpdate() (gas: 41581)
+DestinationRewardManagerSetupTest:test_eventEmittedUponFeePaid() (gas: 259172)
+DestinationRewardManagerSetupTest:test_rejectsZeroLinkAddressOnConstruction() (gas: 59610)
+DestinationRewardManagerSetupTest:test_removeFeeManagerNonExistentAddress() (gas: 12778)
+DestinationRewardManagerSetupTest:test_setFeeManagerZeroAddress() (gas: 17084)
+DestinationRewardManagerUpdateRewardRecipientsMultiplePoolsTest:test_updatePrimaryRecipientWeights() (gas: 376674)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_eventIsEmittedUponUpdateRecipients() (gas: 280411)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_onlyAdminCanUpdateRecipients() (gas: 19705)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_partialUpdateRecipientWeights() (gas: 221004)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateAllRecipientsWithSameAddressAndWeight() (gas: 274233)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsToSubset() (gas: 254156)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithExcessiveWeight() (gas: 259143)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithSameAddressAndWeight() (gas: 149856)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithUnderWeightSet() (gas: 259217)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientWeights() (gas: 372155)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientWithNewZeroAddress() (gas: 270700)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsContainsDuplicateRecipients() (gas: 288483)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentLargerSet() (gas: 407780)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentPartialSet() (gas: 317945)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentSet() (gas: 377692)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentSetWithInvalidWeights() (gas: 312038)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsUpdateAndRemoveExistingForLargerSet() (gas: 399603)
+DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsUpdateAndRemoveExistingForSmallerSet() (gas: 289433)
+DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkLink() (gas: 639153)
+DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNative() (gas: 640232)
+DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrapped() (gas: 661796)
+DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrappedReturnsChange() (gas: 661751)
+DestinationVerifierConstructorTest:test_falseIfIsNotCorrectInterface() (gas: 8719)
+DestinationVerifierConstructorTest:test_revertsIfInitializedWithEmptyVerifierProxy() (gas: 61121)
+DestinationVerifierConstructorTest:test_trueIfIsCorrectInterface() (gas: 8604)
+DestinationVerifierConstructorTest:test_typeAndVersion() (gas: 2818189)
+DestinationVerifierProxyInitializeVerifierTest:test_correctlySetsTheOwner() (gas: 1035181)
+DestinationVerifierProxyInitializeVerifierTest:test_correctlySetsVersion() (gas: 9841)
+DestinationVerifierProxyInitializeVerifierTest:test_setVerifierCalledByNoOwner() (gas: 17483)
+DestinationVerifierProxyInitializeVerifierTest:test_setVerifierOk() (gas: 30622)
+DestinationVerifierProxyInitializeVerifierTest:test_setVerifierWhichDoesntHonourInterface() (gas: 16851)
+DestinationVerifierSetAccessControllerTest:test_emitsTheCorrectEvent() (gas: 35391)
+DestinationVerifierSetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 15089)
+DestinationVerifierSetAccessControllerTest:test_successfullySetsNewAccessController() (gas: 34885)
+DestinationVerifierSetAccessControllerTest:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15007)
+DestinationVerifierSetConfigTest:test_NoDonConfigAlreadyExists() (gas: 2874492)
+DestinationVerifierSetConfigTest:test_addressesAndWeightsDoNotProduceSideEffectsInDonConfigIds() (gas: 1323177)
+DestinationVerifierSetConfigTest:test_donConfigIdIsSameForSignersInDifferentOrder() (gas: 1290374)
+DestinationVerifierSetConfigTest:test_removeLatestConfig() (gas: 786275)
+DestinationVerifierSetConfigTest:test_removeLatestConfigWhenNoConfigShouldFail() (gas: 12870)
+DestinationVerifierSetConfigTest:test_revertsIfCalledByNonOwner() (gas: 174936)
+DestinationVerifierSetConfigTest:test_revertsIfDuplicateSigners() (gas: 171604)
+DestinationVerifierSetConfigTest:test_revertsIfFaultToleranceIsZero() (gas: 168484)
+DestinationVerifierSetConfigTest:test_revertsIfNotEnoughSigners() (gas: 11571)
+DestinationVerifierSetConfigTest:test_revertsIfSetWithTooManySigners() (gas: 17943)
+DestinationVerifierSetConfigTest:test_revertsIfSignerContainsZeroAddress() (gas: 324333)
+DestinationVerifierSetConfigTest:test_setConfigActiveUnknownDonConfigId() (gas: 13102)
+DestinationVerifierSetConfigTest:test_setConfigWithActivationTime() (gas: 1088176)
+DestinationVerifierSetConfigTest:test_setConfigWithActivationTimeEarlierThanLatestConfigShouldFail() (gas: 1963414)
+DestinationVerifierSetConfigTest:test_setConfigWithActivationTimeNoFutureTimeShouldFail() (gas: 259797)
 FeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52645)
 FeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52595)
 FeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78808)
@@ -108,6 +322,7 @@ FeeManagerProcessFeeTest:test_surchargeIsNoLongerAppliedAfterRemoving() (gas: 47
 FeeManagerProcessFeeTest:test_surchargeIsNotAppliedForLinkFee() (gas: 49938)
 FeeManagerProcessFeeTest:test_surchargeIsNotAppliedWith100PercentDiscount() (gas: 78261)
 FeeManagerProcessFeeTest:test_testRevertIfReportHasExpired() (gas: 14919)
+MultiVerifierBillingTests:test_multipleFeeManagersAndVerifiers() (gas: 4586990)
 RewardManagerClaimTest:test_claimAllRecipients() (gas: 277131)
 RewardManagerClaimTest:test_claimMultipleRecipients() (gas: 154341)
 RewardManagerClaimTest:test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() (gas: 330086)
@@ -200,6 +415,13 @@ VerifierActivateFeedTest:test_revertsIfNoFeedExistsActivate() (gas: 13179)
 VerifierActivateFeedTest:test_revertsIfNoFeedExistsDeactivate() (gas: 13157)
 VerifierActivateFeedTest:test_revertsIfNotOwnerActivateFeed() (gas: 17109)
 VerifierActivateFeedTest:test_revertsIfNotOwnerDeactivateFeed() (gas: 17164)
+VerifierBillingTests:test_rewardsAreDistributedAccordingToWeights() (gas: 1731717)
+VerifierBillingTests:test_rewardsAreDistributedAccordingToWeightsMultipleWeigths() (gas: 4460715)
+VerifierBillingTests:test_rewardsAreDistributedAccordingToWeightsUsingHistoricalConfigs() (gas: 2098833)
+VerifierBillingTests:test_verifyWithLinkV3Report() (gas: 1591346)
+VerifierBillingTests:test_verifyWithNativeERC20() (gas: 1467256)
+VerifierBillingTests:test_verifyWithNativeUnwrapped() (gas: 1376447)
+VerifierBillingTests:test_verifyWithNativeUnwrappedReturnsChange() (gas: 1383493)
 VerifierBulkVerifyBillingReport:test_verifyMultiVersions() (gas: 476595)
 VerifierBulkVerifyBillingReport:test_verifyMultiVersionsReturnsVerifiedReports() (gas: 474853)
 VerifierBulkVerifyBillingReport:test_verifyWithBulkLink() (gas: 557541)
@@ -212,6 +434,7 @@ VerifierDeactivateFeedWithVerifyTest:test_currentReportAllowsVerification() (gas
 VerifierDeactivateFeedWithVerifyTest:test_currentReportFailsVerification() (gas: 113388)
 VerifierDeactivateFeedWithVerifyTest:test_previousReportAllowsVerification() (gas: 99624)
 VerifierDeactivateFeedWithVerifyTest:test_previousReportFailsVerification() (gas: 69943)
+VerifierInterfacesTest:test_DestinationContractInterfaces() (gas: 623467)
 VerifierProxyAccessControlledVerificationTest:test_proxiesToTheVerifierIfHasAccess() (gas: 208529)
 VerifierProxyAccessControlledVerificationTest:test_revertsIfNoAccess() (gas: 112345)
 VerifierProxyConstructorTest:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1485359)
@@ -236,6 +459,9 @@ VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_correctlyUnsetsVeri
 VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_emitsAnEventAfterUnsettingVerifier() (gas: 17961)
 VerifierProxyVerifyTest:test_proxiesToTheCorrectVerifier() (gas: 204342)
 VerifierProxyVerifyTest:test_revertsIfNoVerifierConfigured() (gas: 117264)
+VerifierSetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 17196)
+VerifierSetAccessControllerTest:test_setFeeManagerWhichDoesntHonourInterface() (gas: 16571)
+VerifierSetAccessControllerTest:test_successfullySetsNewFeeManager() (gas: 44855)
 VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlySetsConfigWhenDigestsAreRemoved() (gas: 542302)
 VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() (gas: 967768)
 VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlyUpdatesTheDigestInTheProxy() (gas: 523251)
@@ -256,6 +482,9 @@ VerifierTestBillingReport:test_verifyWithLink() (gas: 275293)
 VerifierTestBillingReport:test_verifyWithNative() (gas: 316326)
 VerifierTestBillingReport:test_verifyWithNativeUnwrapped() (gas: 318574)
 VerifierTestBillingReport:test_verifyWithNativeUnwrappedReturnsChange() (gas: 325642)
+VerifierVerifyBulkTest:test_revertsVerifyBulkIfNoAccess() (gas: 112867)
+VerifierVerifyBulkTest:test_verifyBulkSingleCaseWithSingleConfig() (gas: 745046)
+VerifierVerifyBulkTest:test_verifyBulkWithSingleConfigOneVerifyFails() (gas: 698203)
 VerifierVerifyMultipleConfigDigestTest:test_canVerifyNewerReportsWithNewerConfigs() (gas: 133961)
 VerifierVerifyMultipleConfigDigestTest:test_canVerifyOlderReportsWithOlderConfigs() (gas: 189865)
 VerifierVerifyMultipleConfigDigestTest:test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() (gas: 88216)
@@ -270,6 +499,18 @@ VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedByNonProxy() (gas: 10
 VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedWithIncorrectAddresses() (gas: 184077)
 VerifierVerifySingleConfigDigestTest:test_revertsIfWrongNumberOfSigners() (gas: 110042)
 VerifierVerifySingleConfigDigestTest:test_setsTheCorrectEpoch() (gas: 194592)
+VerifierVerifyTest:test_canVerifyNewerReportsWithNewerConfigs() (gas: 861741)
+VerifierVerifyTest:test_canVerifyOlderV3ReportsWithOlderConfigs() (gas: 815984)
+VerifierVerifyTest:test_failToVerifyReportIfDupSigners() (gas: 450715)
+VerifierVerifyTest:test_failToVerifyReportIfNoSigners() (gas: 426492)
+VerifierVerifyTest:test_failToVerifyReportIfNotEnoughSigners() (gas: 434814)
+VerifierVerifyTest:test_failToVerifyReportIfSignerNotInConfig() (gas: 456866)
+VerifierVerifyTest:test_revertsVerifyIfNoAccess() (gas: 109465)
+VerifierVerifyTest:test_rollingOutConfiguration() (gas: 1497254)
+VerifierVerifyTest:test_scenarioRollingNewChainWithHistoricConfigs() (gas: 976162)
+VerifierVerifyTest:test_verifyFailsWhenReportIsOlderThanConfig() (gas: 2303366)
+VerifierVerifyTest:test_verifyReport() (gas: 1434811)
+VerifierVerifyTest:test_verifyTooglingActiveFlagsDonConfigs() (gas: 1918797)
 Verifier_accessControlledVerify:testVerifyWithAccessControl_gas() (gas: 212077)
 Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithLinkFeeSuccess_gas() (gas: 519389)
 Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithNativeFeeSuccess_gas() (gas: 542808)
diff --git a/contracts/scripts/native_solc_compile_all_llo-feeds b/contracts/scripts/native_solc_compile_all_llo-feeds
index eb17f93b0de..f0224d962cc 100755
--- a/contracts/scripts/native_solc_compile_all_llo-feeds
+++ b/contracts/scripts/native_solc_compile_all_llo-feeds
@@ -28,15 +28,19 @@ compileContract () {
       "$ROOT"/contracts/src/v0.8/"$1"
 }
 
-compileContract llo-feeds/Verifier.sol
-compileContract llo-feeds/VerifierProxy.sol
-compileContract llo-feeds/FeeManager.sol
-compileContract llo-feeds/RewardManager.sol
+compileContract llo-feeds/v0.3.0/Verifier.sol
+compileContract llo-feeds/v0.3.0/VerifierProxy.sol
+compileContract llo-feeds/v0.3.0/FeeManager.sol
+compileContract llo-feeds/v0.3.0/RewardManager.sol
+compileContract llo-feeds/v0.4.0/DestinationVerifier.sol
+compileContract llo-feeds/v0.4.0/DestinationVerifierProxy.sol
+compileContract llo-feeds/v0.4.0/DestinationFeeManager.sol
+compileContract llo-feeds/v0.4.0/DestinationRewardManager.sol
 
 
 # Test | Mocks
-compileContract llo-feeds/test/mocks/ErroredVerifier.sol
-compileContract llo-feeds/test/mocks/ExposedVerifier.sol
+compileContract llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol
+compileContract llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol
 
 # LLO
 compileContract llo-feeds/dev/ChannelConfigStore.sol
diff --git a/contracts/src/v0.8/llo-feeds/libraries/Common.sol b/contracts/src/v0.8/llo-feeds/libraries/Common.sol
index f732ced004b..23418bf41a9 100644
--- a/contracts/src/v0.8/llo-feeds/libraries/Common.sol
+++ b/contracts/src/v0.8/llo-feeds/libraries/Common.sol
@@ -19,6 +19,28 @@ library Common {
     uint64 weight;
   }
 
+  /**
+   * @notice Checks if an array of AddressAndWeight has duplicate addresses
+   * @param recipients The array of AddressAndWeight to check
+   * @return bool True if there are duplicates, false otherwise
+   */
+  function _hasDuplicateAddresses(address[] memory recipients) internal pure returns (bool) {
+    for (uint256 i = 0; i < recipients.length; ) {
+      for (uint256 j = i + 1; j < recipients.length; ) {
+        if (recipients[i] == recipients[j]) {
+          return true;
+        }
+        unchecked {
+          ++j;
+        }
+      }
+      unchecked {
+        ++i;
+      }
+    }
+    return false;
+  }
+
   /**
    * @notice Checks if an array of AddressAndWeight has duplicate addresses
    * @param recipients The array of AddressAndWeight to check
@@ -40,4 +62,28 @@ library Common {
     }
     return false;
   }
+
+  /**
+   * @notice sorts a list of addresses numerically
+   * @param arr The array of addresses to sort
+   * @param left the start index
+   * @param right the end index
+   */
+  function _quickSort(address[] memory arr, int256 left, int256 right) internal pure {
+    int256 i = left;
+    int256 j = right;
+    if (i == j) return;
+    address pivot = arr[uint256(left + (right - left) / 2)];
+    while (i <= j) {
+      while (uint160(arr[uint256(i)]) < uint160(pivot)) i++;
+      while (uint160(pivot) < uint160(arr[uint256(j)])) j--;
+      if (i <= j) {
+        (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]);
+        i++;
+        j--;
+      }
+    }
+    if (left < j) _quickSort(arr, left, j);
+    if (i < right) _quickSort(arr, i, right);
+  }
 }
diff --git a/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol b/contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol
similarity index 99%
rename from contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol
rename to contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol
index 21bd957e4e1..8b5343866f6 100644
--- a/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {Test} from "forge-std/Test.sol";
-import {ByteUtil} from "../libraries/ByteUtil.sol";
+import {ByteUtil} from "../ByteUtil.sol";
 
 contract ByteUtilTest is Test {
   using ByteUtil for bytes;
diff --git a/contracts/src/v0.8/llo-feeds/FeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol
similarity index 96%
rename from contracts/src/v0.8/llo-feeds/FeeManager.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol
index 665940d4670..44f550e3253 100644
--- a/contracts/src/v0.8/llo-feeds/FeeManager.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol
@@ -1,16 +1,16 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
 import {IFeeManager} from "./interfaces/IFeeManager.sol";
-import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
-import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "./libraries/Common.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../libraries/Common.sol";
 import {IRewardManager} from "./interfaces/IRewardManager.sol";
-import {IWERC20} from "../shared/interfaces/IWERC20.sol";
-import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
-import {Math} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol";
-import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
+import {IWERC20} from "../../shared/interfaces/IWERC20.sol";
+import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
+import {Math} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol";
+import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
 import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol";
 
 /**
diff --git a/contracts/src/v0.8/llo-feeds/RewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol
similarity index 96%
rename from contracts/src/v0.8/llo-feeds/RewardManager.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol
index 1929289a360..49fef51c569 100644
--- a/contracts/src/v0.8/llo-feeds/RewardManager.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol
@@ -1,12 +1,12 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
 import {IRewardManager} from "./interfaces/IRewardManager.sol";
-import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
-import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
-import {Common} from "./libraries/Common.sol";
-import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
+import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {Common} from "../libraries/Common.sol";
+import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
 
 /**
  * @title RewardManager
diff --git a/contracts/src/v0.8/llo-feeds/Verifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/Verifier.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol
index c8858999dab..fe5742108a5 100644
--- a/contracts/src/v0.8/llo-feeds/Verifier.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol
@@ -1,12 +1,12 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
 import {IVerifier} from "./interfaces/IVerifier.sol";
 import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol";
-import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
-import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "./libraries/Common.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../libraries/Common.sol";
 
 // OCR2 standard
 uint256 constant MAX_NUM_ORACLES = 31;
diff --git a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol
similarity index 95%
rename from contracts/src/v0.8/llo-feeds/VerifierProxy.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol
index c32a27178cc..c06312dd7be 100644
--- a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol
@@ -1,14 +1,14 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
 import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol";
 import {IVerifier} from "./interfaces/IVerifier.sol";
-import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
-import {AccessControllerInterface} from "../shared/interfaces/AccessControllerInterface.sol";
-import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
 import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol";
-import {Common} from "./libraries/Common.sol";
+import {Common} from "../libraries/Common.sol";
 
 /**
  * The verifier proxy contract is the gateway for all report verification requests
diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol
similarity index 94%
rename from contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol
index 4095607b911..818a3a09a4c 100644
--- a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol
@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "../libraries/Common.sol";
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
 import {IVerifierFeeManager} from "./IVerifierFeeManager.sol";
 
 interface IFeeManager is IERC165, IVerifierFeeManager {
diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol
similarity index 94%
rename from contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol
index 5a6e03f1c9a..f08ce34db29 100644
--- a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol
@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "../libraries/Common.sol";
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
 
 interface IRewardManager is IERC165 {
   /**
diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol
index 617d702d3f8..94b260399ea 100644
--- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol
@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "../libraries/Common.sol";
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
 
 interface IVerifier is IERC165 {
   /**
diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol
similarity index 88%
rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol
index 522db952e5d..da3fdfac153 100644
--- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol
@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "../libraries/Common.sol";
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
 
 interface IVerifierFeeManager is IERC165 {
   /**
diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol
similarity index 95%
rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol
index 2eb1b4aff4b..6609e4869ae 100644
--- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol
@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import {Common} from "../libraries/Common.sol";
-import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../libraries/Common.sol";
+import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol";
 import {IVerifierFeeManager} from "./IVerifierFeeManager.sol";
 
 interface IVerifierProxy {
diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol
index edde26b2ee1..0d598cdac52 100644
--- a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol
@@ -4,9 +4,9 @@ pragma solidity 0.8.19;
 import {Test} from "forge-std/Test.sol";
 import {FeeManager} from "../../FeeManager.sol";
 import {RewardManager} from "../../RewardManager.sol";
-import {Common} from "../../libraries/Common.sol";
-import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
-import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol";
 import {IRewardManager} from "../../interfaces/IRewardManager.sol";
 import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol";
 
diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.general.t.sol
similarity index 100%
rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.general.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.general.t.sol
diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol
index 299a7f09d51..1b0f95d51bd 100644
--- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: UNLICENSED
 pragma solidity 0.8.19;
 
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 import "./BaseFeeManager.t.sol";
 
 /**
@@ -10,7 +10,7 @@ import "./BaseFeeManager.t.sol";
  * @notice This contract will test the functionality of the feeManager's getFeeAndReward
  */
 contract FeeManagerProcessFeeTest is BaseFeeManagerTest {
-  function test_baseFeeIsAppliedForNative() public {
+  function test_baseFeeIsAppliedForNative() public view {
     //get the fee required by the feeManager
     Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
 
@@ -18,7 +18,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest {
     assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
   }
 
-  function test_baseFeeIsAppliedForLink() public {
+  function test_baseFeeIsAppliedForLink() public view {
     //get the fee required by the feeManager
     Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
 
@@ -378,7 +378,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest {
     assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount);
   }
 
-  function test_reportWithNoExpiryOrFeeReturnsZero() public {
+  function test_reportWithNoExpiryOrFeeReturnsZero() public view {
     //get the fee required by the feeManager
     Common.Asset memory fee = getFee(getV1Report(DEFAULT_FEED_1_V1), getNativeQuote(), USER);
 
@@ -462,7 +462,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest {
     setNativeSurcharge(nativeSurcharge, ADMIN);
   }
 
-  function test_getBaseRewardWithLinkQuote() public {
+  function test_getBaseRewardWithLinkQuote() public view {
     //get the fee required by the feeManager
     Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
 
@@ -481,7 +481,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest {
     assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2);
   }
 
-  function test_getRewardWithNativeQuote() public {
+  function test_getRewardWithNativeQuote() public view {
     //get the fee required by the feeManager
     Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
 
diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol
similarity index 99%
rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol
index f8c1d47d4d0..0e0ed8977bc 100644
--- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: UNLICENSED
 pragma solidity 0.8.19;
 
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 import "./BaseFeeManager.t.sol";
 import {IRewardManager} from "../../interfaces/IRewardManager.sol";
 
diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFeeBulk.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFeeBulk.t.sol
similarity index 100%
rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFeeBulk.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFeeBulk.t.sol
diff --git a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol
index 29b488fb328..ee8ba4c3e34 100644
--- a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.19;
 
 import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "../verifier/BaseVerifierTest.t.sol";
-import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol";
-import {Common} from "../../libraries/Common.sol";
+import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol";
+import {Common} from "../../../libraries/Common.sol";
 import {IRewardManager} from "../../interfaces/IRewardManager.sol";
 
 contract Verifier_setConfig is BaseTest {
diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol
similarity index 66%
rename from contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol
index 01cb1a50611..e9dcd589e23 100644
--- a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol
@@ -2,13 +2,24 @@
 pragma solidity 0.8.19;
 
 import {IVerifier} from "../../interfaces/IVerifier.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 contract ErroredVerifier is IVerifier {
   function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
     return interfaceId == this.verify.selector;
   }
 
+  //define each of the errors thrown in the revert below
+
+  error FailedToVerify();
+  error FailedToSetConfig();
+  error FailedToActivateConfig();
+  error FailedToDeactivateConfig();
+  error FailedToActivateFeed();
+  error FailedToDeactivateFeed();
+  error FailedToGetLatestConfigDigestAndEpoch();
+  error FailedToGetLatestConfigDetails();
+
   function verify(
     bytes memory,
     /**
@@ -26,7 +37,7 @@ contract ErroredVerifier is IVerifier {
       bytes memory
     )
   {
-    revert("Failed to verify");
+    revert FailedToVerify();
   }
 
   function setConfig(
@@ -39,7 +50,7 @@ contract ErroredVerifier is IVerifier {
     bytes memory,
     Common.AddressAndWeight[] memory
   ) external pure override {
-    revert("Failed to set config");
+    revert FailedToSetConfig();
   }
 
   function setConfigFromSource(
@@ -55,30 +66,30 @@ contract ErroredVerifier is IVerifier {
     bytes memory,
     Common.AddressAndWeight[] memory
   ) external pure override {
-    revert("Failed to set config");
+    revert FailedToSetConfig();
   }
 
   function activateConfig(bytes32, bytes32) external pure {
-    revert("Failed to activate config");
+    revert FailedToActivateConfig();
   }
 
   function deactivateConfig(bytes32, bytes32) external pure {
-    revert("Failed to deactivate config");
+    revert FailedToDeactivateConfig();
   }
 
   function activateFeed(bytes32) external pure {
-    revert("Failed to activate feed");
+    revert FailedToActivateFeed();
   }
 
   function deactivateFeed(bytes32) external pure {
-    revert("Failed to deactivate feed");
+    revert FailedToDeactivateFeed();
   }
 
   function latestConfigDigestAndEpoch(bytes32) external pure override returns (bool, bytes32, uint32) {
-    revert("Failed to get latest config digest and epoch");
+    revert FailedToGetLatestConfigDigestAndEpoch();
   }
 
   function latestConfigDetails(bytes32) external pure override returns (uint32, uint32, bytes32) {
-    revert("Failed to get latest config details");
+    revert FailedToGetLatestConfigDetails();
   }
 }
diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/ExposedVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol
similarity index 100%
rename from contracts/src/v0.8/llo-feeds/test/mocks/ExposedVerifier.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol
diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol
similarity index 61%
rename from contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol
index 16935f69d6b..9abb4c50c29 100644
--- a/contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol
@@ -1,20 +1,20 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.19;
 
-import "../../interfaces/IFeeManager.sol";
+import {IFeeManager} from "../../interfaces/IFeeManager.sol";
 
 contract FeeManagerProxy {
-  IFeeManager internal i_feeManager;
+  IFeeManager internal s_feeManager;
 
   function processFee(bytes calldata payload, bytes calldata parameterPayload) public payable {
-    i_feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender);
+    s_feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender);
   }
 
   function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload) public payable {
-    i_feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender);
+    s_feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender);
   }
 
   function setFeeManager(IFeeManager feeManager) public {
-    i_feeManager = feeManager;
+    s_feeManager = feeManager;
   }
 }
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol
index 65481513f0d..c7bd7528c31 100644
--- a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol
@@ -2,9 +2,9 @@
 pragma solidity 0.8.19;
 
 import {Test} from "forge-std/Test.sol";
-import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
-import {RewardManager} from "../../RewardManager.sol";
-import {Common} from "../../libraries/Common.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {RewardManager} from "../../../v0.3.0/RewardManager.sol";
+import {Common} from "../../../libraries/Common.sol";
 import {IRewardManager} from "../../interfaces/IRewardManager.sol";
 
 /**
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol
similarity index 99%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol
index 5f07d36c72c..efbe9fd6b36 100644
--- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 /**
  * @title BaseRewardManagerTest
@@ -574,7 +574,7 @@ contract RewardManagerRecipientClaimMultiplePoolsTest is BaseRewardManagerTest {
     assertEq(poolIds[1], ZERO_POOL_ID);
   }
 
-  function test_getRewardsAvailableToRecipientInNoPools() public {
+  function test_getRewardsAvailableToRecipientInNoPools() public view {
     //get index 0 as this recipient is in both default pools
     bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, 0, type(uint256).max);
 
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol
similarity index 92%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol
index 7fde76d5281..baff3887699 100644
--- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.19;
 
 import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol";
-import {RewardManager} from "../../RewardManager.sol";
-import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {RewardManager} from "../../../v0.3.0/RewardManager.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
 import {IRewardManager} from "../../interfaces/IRewardManager.sol";
 
 /**
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.payRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.payRecipients.t.sol
similarity index 100%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.payRecipients.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.payRecipients.t.sol
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol
index 1cf5b51f621..d3e6990bd9f 100644
--- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 /**
  * @title BaseRewardManagerTest
diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol
similarity index 99%
rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol
index 6c51a0fbfdd..0d3a2b69b39 100644
--- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 /**
  * @title BaseRewardManagerTest
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol
index daf31875036..4d65414676e 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol
@@ -3,17 +3,16 @@ pragma solidity 0.8.19;
 
 import {Test} from "forge-std/Test.sol";
 import {VerifierProxy} from "../../VerifierProxy.sol";
-import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
 import {IVerifier} from "../../interfaces/IVerifier.sol";
 import {ErroredVerifier} from "../mocks/ErroredVerifier.sol";
 import {Verifier} from "../../Verifier.sol";
 import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
-import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol";
-import {FeeManager} from "../../FeeManager.sol";
-import {Common} from "../../libraries/Common.sol";
-import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
-import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
 import {FeeManager} from "../../FeeManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol";
 import {RewardManager} from "../../RewardManager.sol";
 
 contract BaseTest is Test {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol
index f53c26ba192..99daabe206b 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTestWithConfiguredVerifierAndFeeManager, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
 
 contract VerifierActivateConfigTest is BaseTestWithConfiguredVerifierAndFeeManager {
   function test_revertsIfNotOwner() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol
index 97647c88635..fb52c1c93e7 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTestWithConfiguredVerifierAndFeeManager, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
 
 contract VerifierActivateFeedTest is BaseTestWithConfiguredVerifierAndFeeManager {
   function test_revertsIfNotOwnerActivateFeed() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol
similarity index 77%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol
index b085dc8a651..82efd8907be 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.19;
 
 import {BaseTest} from "./BaseVerifierTest.t.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
-import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
 
 contract VerifierProxyConstructorTest is BaseTest {
   function test_correctlySetsTheOwner() public {
@@ -17,7 +17,7 @@ contract VerifierProxyConstructorTest is BaseTest {
     assertEq(address(proxy.s_accessController()), accessControllerAddr);
   }
 
-  function test_correctlySetsVersion() public {
+  function test_correctlySetsVersion() public view {
     string memory version = s_verifierProxy.typeAndVersion();
     assertEq(version, "VerifierProxy 2.0.0");
   }
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol
similarity index 93%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol
index e02b14fe56e..5537d273be9 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTest} from "./BaseVerifierTest.t.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
 
 contract VerifierProxyInitializeVerifierTest is BaseTest {
   bytes32 latestDigest;
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol
similarity index 92%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol
index 04889e0d5f4..03bd6d97ee5 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTest} from "./BaseVerifierTest.t.sol";
-import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
 
 contract VerifierProxySetAccessControllerTest is BaseTest {
   event AccessControllerSet(address oldAccessController, address newAccessController);
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol
similarity index 88%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol
index ea23f880ba7..78e5ff0766f 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol
@@ -3,9 +3,9 @@ pragma solidity 0.8.19;
 
 import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol";
 import {IVerifier} from "../../interfaces/IVerifier.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
-import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
-import {Common} from "../../libraries/Common.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
+import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 contract VerifierProxyInitializeVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager {
   function test_revertsIfNotCorrectVerifier() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol
similarity index 87%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol
index ea7e02d7409..441626e575b 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.19;
 
 import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
-import {FeeManager} from "../../FeeManager.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
+import {FeeManager} from "../../../v0.3.0/FeeManager.sol";
 
 contract VerifierProxyInitializeVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager {
   function test_setFeeManagerZeroAddress() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol
similarity index 95%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol
index 746aa955748..a51c67e336c 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
 
 contract VerifierProxyUnsetVerifierTest is BaseTest {
   function test_revertsIfNotAdmin() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol
index 0cd5902161d..9ee9b5272a7 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 contract VerifierSetConfigFromSourceTest is BaseTest {
   function setUp() public virtual override {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol
similarity index 98%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol
index a4e15dcdd4c..972ead81230 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.19;
 
 import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 contract VerifierSetConfigTest is BaseTest {
   function setUp() public virtual override {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol
similarity index 88%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol
index 2857b8f4d3d..81f65f0c6eb 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTest} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
 
 contract VerifierConstructorTest is BaseTest {
   function test_revertsIfInitializedWithEmptyVerifierProxy() public {
@@ -30,12 +30,12 @@ contract VerifierConstructorTest is BaseTest {
 }
 
 contract VerifierSupportsInterfaceTest is BaseTest {
-  function test_falseIfIsNotCorrectInterface() public {
+  function test_falseIfIsNotCorrectInterface() public view {
     bool isInterface = s_verifier.supportsInterface(bytes4("abcd"));
     assertEq(isInterface, false);
   }
 
-  function test_trueIfIsCorrectInterface() public {
+  function test_trueIfIsCorrectInterface() public view {
     bool isInterface = s_verifier.supportsInterface(Verifier.verify.selector);
     assertEq(isInterface, true);
   }
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTestBillingReport.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTestBillingReport.t.sol
similarity index 100%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierTestBillingReport.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTestBillingReport.t.sol
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol
index cc3c33331df..e192a2e9e08 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol
@@ -2,7 +2,7 @@
 pragma solidity 0.8.19;
 
 import {BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
 
 contract VerificationdeactivateConfigWhenThereAreMultipleDigestsTest is BaseTestWithMultipleConfiguredDigests {
   function test_revertsIfCalledByNonOwner() public {
diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol
similarity index 97%
rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol
rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol
index db7be5ca543..1c14ba974c0 100644
--- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol
+++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol
@@ -2,10 +2,10 @@
 pragma solidity 0.8.19;
 
 import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol";
-import {Verifier} from "../../Verifier.sol";
-import {VerifierProxy} from "../../VerifierProxy.sol";
-import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol";
-import {Common} from "../../libraries/Common.sol";
+import {Verifier} from "../../../v0.3.0/Verifier.sol";
+import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
 
 contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager {
   bytes32[3] internal s_reportContext;
@@ -32,7 +32,7 @@ contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager {
     );
   }
 
-  function assertReportsEqual(bytes memory response, V1Report memory testReport) public {
+  function assertReportsEqual(bytes memory response, V1Report memory testReport) public pure {
     (
       bytes32 feedId,
       uint32 timestamp,
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol
new file mode 100644
index 00000000000..38d93de5cb7
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol
@@ -0,0 +1,557 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../libraries/Common.sol";
+import {IWERC20} from "../../shared/interfaces/IWERC20.sol";
+import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
+import {Math} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol";
+import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
+import {IDestinationRewardManager} from "./interfaces/IDestinationRewardManager.sol";
+import {IDestinationFeeManager} from "./interfaces/IDestinationFeeManager.sol";
+
+/**
+ * @title FeeManager
+ * @author Michael Fletcher
+ * @author Austin Born
+ * @notice This contract is used for the handling of fees required for users verifying reports.
+ */
+contract DestinationFeeManager is IDestinationFeeManager, ConfirmedOwner, TypeAndVersionInterface {
+  using SafeERC20 for IERC20;
+
+  /// @notice list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token]
+  mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts;
+
+  /// @notice keep track of any subsidised link that is owed to the reward manager.
+  mapping(bytes32 => uint256) public s_linkDeficit;
+
+  /// @notice the total discount that can be applied to a fee, 1e18 = 100% discount
+  uint64 private constant PERCENTAGE_SCALAR = 1e18;
+
+  /// @notice the LINK token address
+  address public immutable i_linkAddress;
+
+  /// @notice the native token address
+  address public immutable i_nativeAddress;
+
+  /// @notice the verifier address
+  mapping(address => address) public s_verifierAddressList;
+
+  /// @notice the reward manager address
+  IDestinationRewardManager public i_rewardManager;
+
+  // @notice the mask to apply to get the report version
+  bytes32 private constant REPORT_VERSION_MASK = 0xffff000000000000000000000000000000000000000000000000000000000000;
+
+  // @notice the different report versions
+  bytes32 private constant REPORT_V1 = 0x0001000000000000000000000000000000000000000000000000000000000000;
+
+  /// @notice the surcharge fee to be paid if paying in native
+  uint256 public s_nativeSurcharge;
+
+  /// @notice the error thrown if the discount or surcharge is invalid
+  error InvalidSurcharge();
+
+  /// @notice the error thrown if the discount is invalid
+  error InvalidDiscount();
+
+  /// @notice the error thrown if the address is invalid
+  error InvalidAddress();
+
+  /// @notice thrown if msg.value is supplied with a bad quote
+  error InvalidDeposit();
+
+  /// @notice thrown if a report has expired
+  error ExpiredReport();
+
+  /// @notice thrown if a report has no quote
+  error InvalidQuote();
+
+  // @notice thrown when the caller is not authorized
+  error Unauthorized();
+
+  // @notice thrown when trying to clear a zero deficit
+  error ZeroDeficit();
+
+  /// @notice thrown when trying to pay an address that cannot except funds
+  error InvalidReceivingAddress();
+
+  /// @notice thrown when trying to bulk verify reports where theres not a matching number of poolIds
+  error PoolIdMismatch();
+
+  /// @notice Emitted whenever a subscriber's discount is updated
+  /// @param subscriber address of the subscriber to update discounts for
+  /// @param feedId Feed ID for the discount
+  /// @param token Token address for the discount
+  /// @param discount Discount to apply, in relation to the PERCENTAGE_SCALAR
+  event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount);
+
+  /// @notice Emitted when updating the native surcharge
+  /// @param newSurcharge Surcharge amount to apply relative to PERCENTAGE_SCALAR
+  event NativeSurchargeUpdated(uint64 newSurcharge);
+
+  /// @notice Emits when this contract does not have enough LINK to send to the reward manager when paying in native
+  /// @param rewards Config digest and link fees which could not be subsidised
+  event InsufficientLink(IDestinationRewardManager.FeePayment[] rewards);
+
+  /// @notice Emitted when funds are withdrawn
+  /// @param adminAddress Address of the admin
+  /// @param recipient Address of the recipient
+  /// @param assetAddress Address of the asset withdrawn
+  /// @param quantity Amount of the asset withdrawn
+  event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity);
+
+  /// @notice Emits when a deficit has been cleared for a particular config digest
+  /// @param configDigest Config digest of the deficit cleared
+  /// @param linkQuantity Amount of LINK required to pay the deficit
+  event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity);
+
+  /// @notice Emits when a fee has been processed
+  /// @param configDigest Config digest of the fee processed
+  /// @param subscriber Address of the subscriber who paid the fee
+  /// @param fee Fee paid
+  /// @param reward Reward paid
+  /// @param appliedDiscount Discount applied to the fee
+  event DiscountApplied(
+    bytes32 indexed configDigest,
+    address indexed subscriber,
+    Common.Asset fee,
+    Common.Asset reward,
+    uint256 appliedDiscount
+  );
+
+  /**
+   * @notice Construct the FeeManager contract
+   * @param _linkAddress The address of the LINK token
+   * @param _nativeAddress The address of the wrapped ERC-20 version of the native token (represents fee in native or wrapped)
+   * @param _verifierAddress The address of the verifier contract
+   * @param _rewardManagerAddress The address of the reward manager contract
+   */
+  constructor(
+    address _linkAddress,
+    address _nativeAddress,
+    address _verifierAddress,
+    address _rewardManagerAddress
+  ) ConfirmedOwner(msg.sender) {
+    if (
+      _linkAddress == address(0) ||
+      _nativeAddress == address(0) ||
+      _verifierAddress == address(0) ||
+      _rewardManagerAddress == address(0)
+    ) revert InvalidAddress();
+
+    i_linkAddress = _linkAddress;
+    i_nativeAddress = _nativeAddress;
+    s_verifierAddressList[_verifierAddress] = _verifierAddress;
+    i_rewardManager = IDestinationRewardManager(_rewardManagerAddress);
+
+    IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max);
+  }
+
+  modifier onlyOwnerOrVerifier() {
+    if (msg.sender != s_verifierAddressList[msg.sender] && msg.sender != owner()) revert Unauthorized();
+    _;
+  }
+
+  modifier onlyVerifier() {
+    if (msg.sender != s_verifierAddressList[msg.sender]) revert Unauthorized();
+    _;
+  }
+
+  /// @inheritdoc TypeAndVersionInterface
+  function typeAndVersion() external pure override returns (string memory) {
+    return "DestinationFeeManager 1.0.0";
+  }
+
+  /// @inheritdoc IERC165
+  function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
+    //for each function in IDestinationFeeManager we need to check if it matches the selector
+    return
+      interfaceId == this.getFeeAndReward.selector ||
+      interfaceId == this.setNativeSurcharge.selector ||
+      interfaceId == this.updateSubscriberDiscount.selector ||
+      interfaceId == this.withdraw.selector ||
+      interfaceId == this.linkAvailableForPayment.selector ||
+      interfaceId == this.payLinkDeficit.selector ||
+      interfaceId == this.addVerifier.selector ||
+      interfaceId == this.removeVerifier.selector ||
+      interfaceId == this.processFee.selector ||
+      interfaceId == this.processFeeBulk.selector ||
+      interfaceId == this.setFeeRecipients.selector;
+  }
+
+  function processFee(
+    bytes32 recipient,
+    bytes calldata payload,
+    bytes calldata parameterPayload,
+    address subscriber
+  ) external payable override onlyVerifier {
+    (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _calculateFee(
+      payload,
+      parameterPayload,
+      subscriber
+    );
+
+    if (fee.amount == 0) {
+      _tryReturnChange(subscriber, msg.value);
+      return;
+    }
+
+    IDestinationFeeManager.FeeAndReward[] memory feeAndReward = new IDestinationFeeManager.FeeAndReward[](1);
+    feeAndReward[0] = IDestinationFeeManager.FeeAndReward(recipient, fee, reward, appliedDiscount);
+
+    if (fee.assetAddress == i_linkAddress) {
+      _handleFeesAndRewards(subscriber, feeAndReward, 1, 0);
+    } else {
+      _handleFeesAndRewards(subscriber, feeAndReward, 0, 1);
+    }
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function processFeeBulk(
+    bytes32[] memory poolIds,
+    bytes[] calldata payloads,
+    bytes calldata parameterPayload,
+    address subscriber
+  ) external payable override onlyVerifier {
+    //poolIDs are mapped to payloads, so they should be the same length
+    if (poolIds.length != payloads.length) revert PoolIdMismatch();
+
+    IDestinationFeeManager.FeeAndReward[] memory feesAndRewards = new IDestinationFeeManager.FeeAndReward[](
+      payloads.length
+    );
+
+    //keep track of the number of fees to prevent over initialising the FeePayment array within _convertToLinkAndNativeFees
+    uint256 numberOfLinkFees;
+    uint256 numberOfNativeFees;
+
+    uint256 feesAndRewardsIndex;
+    for (uint256 i; i < payloads.length; ++i) {
+      if (poolIds[i] == bytes32(0)) revert InvalidAddress();
+
+      (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _calculateFee(
+        payloads[i],
+        parameterPayload,
+        subscriber
+      );
+
+      if (fee.amount != 0) {
+        feesAndRewards[feesAndRewardsIndex++] = IDestinationFeeManager.FeeAndReward(
+          poolIds[i],
+          fee,
+          reward,
+          appliedDiscount
+        );
+
+        unchecked {
+          //keep track of some tallys to make downstream calculations more efficient
+          if (fee.assetAddress == i_linkAddress) {
+            ++numberOfLinkFees;
+          } else {
+            ++numberOfNativeFees;
+          }
+        }
+      }
+    }
+
+    if (numberOfLinkFees != 0 || numberOfNativeFees != 0) {
+      _handleFeesAndRewards(subscriber, feesAndRewards, numberOfLinkFees, numberOfNativeFees);
+    } else {
+      _tryReturnChange(subscriber, msg.value);
+    }
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function getFeeAndReward(
+    address subscriber,
+    bytes memory report,
+    address quoteAddress
+  ) public view returns (Common.Asset memory, Common.Asset memory, uint256) {
+    Common.Asset memory fee;
+    Common.Asset memory reward;
+
+    //get the feedId from the report
+    bytes32 feedId = bytes32(report);
+
+    //the report needs to be a support version
+    bytes32 reportVersion = _getReportVersion(feedId);
+
+    //version 1 of the reports don't require quotes, so the fee will be 0
+    if (reportVersion == REPORT_V1) {
+      fee.assetAddress = i_nativeAddress;
+      reward.assetAddress = i_linkAddress;
+      return (fee, reward, 0);
+    }
+
+    //verify the quote payload is a supported token
+    if (quoteAddress != i_nativeAddress && quoteAddress != i_linkAddress) {
+      revert InvalidQuote();
+    }
+
+    //decode the report depending on the version
+    uint256 linkQuantity;
+    uint256 nativeQuantity;
+    uint256 expiresAt;
+    (, , , nativeQuantity, linkQuantity, expiresAt) = abi.decode(
+      report,
+      (bytes32, uint32, uint32, uint192, uint192, uint32)
+    );
+
+    //read the timestamp bytes from the report data and verify it has not expired
+    if (expiresAt < block.timestamp) {
+      revert ExpiredReport();
+    }
+
+    //get the discount being applied
+    uint256 discount = s_subscriberDiscounts[subscriber][feedId][quoteAddress];
+
+    //the reward is always set in LINK
+    reward.assetAddress = i_linkAddress;
+    reward.amount = Math.ceilDiv(linkQuantity * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR);
+
+    //calculate either the LINK fee or native fee if it's within the report
+    if (quoteAddress == i_linkAddress) {
+      fee.assetAddress = i_linkAddress;
+      fee.amount = reward.amount;
+    } else {
+      uint256 surchargedFee = Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR);
+
+      fee.assetAddress = i_nativeAddress;
+      fee.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR);
+    }
+
+    //return the fee
+    return (fee, reward, discount);
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function setFeeRecipients(
+    bytes32 configDigest,
+    Common.AddressAndWeight[] calldata rewardRecipientAndWeights
+  ) external onlyOwnerOrVerifier {
+    i_rewardManager.setRewardRecipients(configDigest, rewardRecipientAndWeights);
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function setNativeSurcharge(uint64 surcharge) external onlyOwner {
+    if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge();
+
+    s_nativeSurcharge = surcharge;
+
+    emit NativeSurchargeUpdated(surcharge);
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function updateSubscriberDiscount(
+    address subscriber,
+    bytes32 feedId,
+    address token,
+    uint64 discount
+  ) external onlyOwner {
+    //make sure the discount is not greater than the total discount that can be applied
+    if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount();
+    //make sure the token is either LINK or native
+    if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress();
+
+    s_subscriberDiscounts[subscriber][feedId][token] = discount;
+
+    emit SubscriberDiscountUpdated(subscriber, feedId, token, discount);
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function withdraw(address assetAddress, address recipient, uint192 quantity) external onlyOwner {
+    //address 0 is used to withdraw native in the context of withdrawing
+    if (assetAddress == address(0)) {
+      (bool success, ) = payable(recipient).call{value: quantity}("");
+
+      if (!success) revert InvalidReceivingAddress();
+      return;
+    }
+
+    //withdraw the requested asset
+    IERC20(assetAddress).safeTransfer(recipient, quantity);
+
+    //emit event when funds are withdrawn
+    emit Withdraw(msg.sender, recipient, assetAddress, uint192(quantity));
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function linkAvailableForPayment() external view returns (uint256) {
+    //return the amount of LINK this contact has available to pay rewards
+    return IERC20(i_linkAddress).balanceOf(address(this));
+  }
+
+  /**
+   * @notice Gets the current version of the report that is encoded as the last two bytes of the feed
+   * @param feedId feed id to get the report version for
+   */
+  function _getReportVersion(bytes32 feedId) internal pure returns (bytes32) {
+    return REPORT_VERSION_MASK & feedId;
+  }
+
+  function _calculateFee(
+    bytes calldata payload,
+    bytes calldata parameterPayload,
+    address subscriber
+  ) internal view returns (Common.Asset memory, Common.Asset memory, uint256) {
+    if (subscriber == address(this)) revert InvalidAddress();
+
+    //decode the report from the payload
+    (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes));
+
+    //get the feedId from the report
+    bytes32 feedId = bytes32(report);
+
+    //v1 doesn't need a quote payload, so skip the decoding
+    address quote;
+    if (_getReportVersion(feedId) != REPORT_V1) {
+      //decode the quote from the bytes
+      (quote) = abi.decode(parameterPayload, (address));
+    }
+
+    //decode the fee, it will always be native or LINK
+    return getFeeAndReward(subscriber, report, quote);
+  }
+
+  function _handleFeesAndRewards(
+    address subscriber,
+    IDestinationFeeManager.FeeAndReward[] memory feesAndRewards,
+    uint256 numberOfLinkFees,
+    uint256 numberOfNativeFees
+  ) internal {
+    IDestinationRewardManager.FeePayment[] memory linkRewards = new IDestinationRewardManager.FeePayment[](
+      numberOfLinkFees
+    );
+    IDestinationRewardManager.FeePayment[] memory nativeFeeLinkRewards = new IDestinationRewardManager.FeePayment[](
+      numberOfNativeFees
+    );
+
+    uint256 totalNativeFee;
+    uint256 totalNativeFeeLinkValue;
+
+    uint256 linkRewardsIndex;
+    uint256 nativeFeeLinkRewardsIndex;
+
+    uint256 totalNumberOfFees = numberOfLinkFees + numberOfNativeFees;
+    for (uint256 i; i < totalNumberOfFees; ++i) {
+      if (feesAndRewards[i].fee.assetAddress == i_linkAddress) {
+        linkRewards[linkRewardsIndex++] = IDestinationRewardManager.FeePayment(
+          feesAndRewards[i].configDigest,
+          uint192(feesAndRewards[i].reward.amount)
+        );
+      } else {
+        nativeFeeLinkRewards[nativeFeeLinkRewardsIndex++] = IDestinationRewardManager.FeePayment(
+          feesAndRewards[i].configDigest,
+          uint192(feesAndRewards[i].reward.amount)
+        );
+        totalNativeFee += feesAndRewards[i].fee.amount;
+        totalNativeFeeLinkValue += feesAndRewards[i].reward.amount;
+      }
+
+      if (feesAndRewards[i].appliedDiscount != 0) {
+        emit DiscountApplied(
+          feesAndRewards[i].configDigest,
+          subscriber,
+          feesAndRewards[i].fee,
+          feesAndRewards[i].reward,
+          feesAndRewards[i].appliedDiscount
+        );
+      }
+    }
+
+    //keep track of change in case of any over payment
+    uint256 change;
+
+    if (msg.value != 0) {
+      //there must be enough to cover the fee
+      if (totalNativeFee > msg.value) revert InvalidDeposit();
+
+      //wrap the amount required to pay the fee & approve as the subscriber paid in wrapped native
+      IWERC20(i_nativeAddress).deposit{value: totalNativeFee}();
+
+      unchecked {
+        //msg.value is always >= to fee.amount
+        change = msg.value - totalNativeFee;
+      }
+    } else {
+      if (totalNativeFee != 0) {
+        //subscriber has paid in wrapped native, so transfer the native to this contract
+        IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), totalNativeFee);
+      }
+    }
+
+    if (linkRewards.length != 0) {
+      i_rewardManager.onFeePaid(linkRewards, subscriber);
+    }
+
+    if (nativeFeeLinkRewards.length != 0) {
+      //distribute subsidised fees paid in Native
+      if (totalNativeFeeLinkValue > IERC20(i_linkAddress).balanceOf(address(this))) {
+        // If not enough LINK on this contract to forward for rewards, tally the deficit to be paid by out-of-band LINK
+        for (uint256 i; i < nativeFeeLinkRewards.length; ++i) {
+          unchecked {
+            //we have previously tallied the fees, any overflows would have already reverted
+            s_linkDeficit[nativeFeeLinkRewards[i].poolId] += nativeFeeLinkRewards[i].amount;
+          }
+        }
+
+        emit InsufficientLink(nativeFeeLinkRewards);
+      } else {
+        //distribute the fees
+        i_rewardManager.onFeePaid(nativeFeeLinkRewards, address(this));
+      }
+    }
+
+    // a refund may be needed if the payee has paid in excess of the fee
+    _tryReturnChange(subscriber, change);
+  }
+
+  function _tryReturnChange(address subscriber, uint256 quantity) internal {
+    if (quantity != 0) {
+      payable(subscriber).transfer(quantity);
+    }
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function payLinkDeficit(bytes32 configDigest) external onlyOwner {
+    uint256 deficit = s_linkDeficit[configDigest];
+
+    if (deficit == 0) revert ZeroDeficit();
+
+    delete s_linkDeficit[configDigest];
+
+    IDestinationRewardManager.FeePayment[] memory deficitFeePayment = new IDestinationRewardManager.FeePayment[](1);
+
+    deficitFeePayment[0] = IDestinationRewardManager.FeePayment(configDigest, uint192(deficit));
+
+    i_rewardManager.onFeePaid(deficitFeePayment, address(this));
+
+    emit LinkDeficitCleared(configDigest, deficit);
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function addVerifier(address verifierAddress) external onlyOwner {
+    if (verifierAddress == address(0)) revert InvalidAddress();
+    //check doesn't already exist
+    if (s_verifierAddressList[verifierAddress] != address(0)) revert InvalidAddress();
+    s_verifierAddressList[verifierAddress] = verifierAddress;
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function removeVerifier(address verifierAddress) external onlyOwner {
+    if (verifierAddress == address(0)) revert InvalidAddress();
+    //check doesn't already exist
+    if (s_verifierAddressList[verifierAddress] == address(0)) revert InvalidAddress();
+    delete s_verifierAddressList[verifierAddress];
+  }
+
+  /// @inheritdoc IDestinationFeeManager
+  function setRewardManager(address rewardManagerAddress) external onlyOwner {
+    if (rewardManagerAddress == address(0)) revert InvalidAddress();
+    IERC20(i_linkAddress).approve(address(i_rewardManager), 0);
+    i_rewardManager = IDestinationRewardManager(rewardManagerAddress);
+    IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol
new file mode 100644
index 00000000000..ae40a2385c7
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
+import {IDestinationRewardManager} from "./interfaces/IDestinationRewardManager.sol";
+import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {Common} from "../libraries/Common.sol";
+import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
+
+/**
+ * @title DestinationRewardManager
+ * @author Michael Fletcher
+ * @author Austin Born
+ * @notice This contract will be used to reward any configured recipients within a pool. Recipients will receive a share of their pool relative to their configured weight.
+ */
+contract DestinationRewardManager is IDestinationRewardManager, ConfirmedOwner, TypeAndVersionInterface {
+  using SafeERC20 for IERC20;
+
+  // @dev The mapping of total fees collected for a particular pot: s_totalRewardRecipientFees[poolId]
+  mapping(bytes32 => uint256) public s_totalRewardRecipientFees;
+
+  // @dev The mapping of fee balances for each pot last time the recipient claimed: s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient]
+  mapping(bytes32 => mapping(address => uint256)) public s_totalRewardRecipientFeesLastClaimedAmounts;
+
+  // @dev The mapping of RewardRecipient weights for a particular poolId: s_rewardRecipientWeights[poolId][rewardRecipient].
+  mapping(bytes32 => mapping(address => uint256)) public s_rewardRecipientWeights;
+
+  // @dev Keep track of the reward recipient weights that have been set to prevent duplicates
+  mapping(bytes32 => bool) public s_rewardRecipientWeightsSet;
+
+  // @dev Store a list of pool ids that have been registered, to make off chain lookups easier
+  bytes32[] public s_registeredPoolIds;
+
+  // @dev The address for the LINK contract
+  address public immutable i_linkAddress;
+
+  // The total weight of all RewardRecipients. 1e18 = 100% of the pool fees
+  uint64 private constant PERCENTAGE_SCALAR = 1e18;
+
+  // The fee manager address
+  mapping(address => address) public s_feeManagerAddressList;
+
+  // @notice Thrown whenever the RewardRecipient weights are invalid
+  error InvalidWeights();
+
+  // @notice Thrown when any given address is invalid
+  error InvalidAddress();
+
+  // @notice Thrown when the pool id is invalid
+  error InvalidPoolId();
+
+  // @notice Thrown when the calling contract is not within the authorized contracts
+  error Unauthorized();
+
+  // @notice Thrown when getAvailableRewardPoolIds parameters are incorrectly set
+  error InvalidPoolLength();
+
+  // Events emitted upon state change
+  event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients);
+  event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity);
+  event FeeManagerUpdated(address newFeeManagerAddress);
+  event FeePaid(FeePayment[] payments, address payer);
+
+  /**
+   * @notice Constructor
+   * @param linkAddress address of the wrapped LINK token
+   */
+  constructor(address linkAddress) ConfirmedOwner(msg.sender) {
+    //ensure that the address ia not zero
+    if (linkAddress == address(0)) revert InvalidAddress();
+
+    i_linkAddress = linkAddress;
+  }
+
+  // @inheritdoc TypeAndVersionInterface
+  function typeAndVersion() external pure override returns (string memory) {
+    return "RewardManager 1.0.0";
+  }
+
+  // @inheritdoc IERC165
+  function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
+    return
+      interfaceId == this.claimRewards.selector ||
+      interfaceId == this.setRewardRecipients.selector ||
+      interfaceId == this.updateRewardRecipients.selector ||
+      interfaceId == this.payRecipients.selector ||
+      interfaceId == this.addFeeManager.selector ||
+      interfaceId == this.removeFeeManager.selector ||
+      interfaceId == this.getAvailableRewardPoolIds.selector ||
+      interfaceId == this.onFeePaid.selector;
+  }
+
+  modifier onlyOwnerOrFeeManager() {
+    if (msg.sender != owner() && msg.sender != s_feeManagerAddressList[msg.sender]) revert Unauthorized();
+    _;
+  }
+
+  modifier onlyOwnerOrRecipientInPool(bytes32 poolId) {
+    if (msg.sender != owner() && s_rewardRecipientWeights[poolId][msg.sender] == 0) revert Unauthorized();
+    _;
+  }
+
+  modifier onlyFeeManager() {
+    if (msg.sender != s_feeManagerAddressList[msg.sender]) revert Unauthorized();
+    _;
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function onFeePaid(FeePayment[] calldata payments, address payer) external override onlyFeeManager {
+    uint256 totalFeeAmount;
+    for (uint256 i; i < payments.length; ++i) {
+      unchecked {
+        //the total amount for any ERC-20 asset cannot exceed 2^256 - 1
+        //see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/36bf1e46fa811f0f07d38eb9cfbc69a955f300ce/contracts/token/ERC20/ERC20.sol#L266
+        //for example implementation.
+        s_totalRewardRecipientFees[payments[i].poolId] += payments[i].amount;
+
+        //tally the total payable fees
+        totalFeeAmount += payments[i].amount;
+      }
+    }
+
+    //transfer the fees to this contract
+    IERC20(i_linkAddress).safeTransferFrom(payer, address(this), totalFeeAmount);
+
+    emit FeePaid(payments, payer);
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function claimRewards(bytes32[] memory poolIds) external override {
+    _claimRewards(msg.sender, poolIds);
+  }
+
+  // wrapper impl for claimRewards
+  function _claimRewards(address recipient, bytes32[] memory poolIds) internal returns (uint256) {
+    //get the total amount claimable for this recipient
+    uint256 claimAmount;
+
+    //loop and claim all the rewards in the poolId pot
+    for (uint256 i; i < poolIds.length; ++i) {
+      //get the poolId to be claimed
+      bytes32 poolId = poolIds[i];
+
+      //get the total fees for the pot
+      uint256 totalFeesInPot = s_totalRewardRecipientFees[poolId];
+
+      unchecked {
+        //avoid unnecessary storage reads if there's no fees in the pot
+        if (totalFeesInPot == 0) continue;
+
+        //get the claimable amount for this recipient, this calculation will never exceed the amount in the pot
+        uint256 claimableAmount = totalFeesInPot - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient];
+
+        //calculate the recipients share of the fees, which is their weighted share of the difference between the last amount they claimed and the current amount in the pot. This can never be more than the total amount in existence
+        uint256 recipientShare = (claimableAmount * s_rewardRecipientWeights[poolId][recipient]) / PERCENTAGE_SCALAR;
+
+        //if there's no fees to claim, continue as there's nothing to update
+        if (recipientShare == 0) continue;
+
+        //keep track of the total amount claimable, this can never be more than the total amount in existence
+        claimAmount += recipientShare;
+
+        //set the current total amount of fees in the pot as it's used to calculate future claims
+        s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] = totalFeesInPot;
+
+        //emit event if the recipient has rewards to claim
+        emit RewardsClaimed(poolIds[i], recipient, uint192(recipientShare));
+      }
+    }
+
+    //check if there's any rewards to claim in the given poolId
+    if (claimAmount != 0) {
+      //transfer the reward to the recipient
+      IERC20(i_linkAddress).safeTransfer(recipient, claimAmount);
+    }
+
+    return claimAmount;
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function setRewardRecipients(
+    bytes32 poolId,
+    Common.AddressAndWeight[] calldata rewardRecipientAndWeights
+  ) external override onlyOwnerOrFeeManager {
+    //revert if there are no recipients to set
+    if (rewardRecipientAndWeights.length == 0) revert InvalidAddress();
+
+    //check that the weights have not been previously set
+    if (s_rewardRecipientWeightsSet[poolId]) revert InvalidPoolId();
+
+    //keep track of the registered poolIds to make off chain lookups easier
+    s_registeredPoolIds.push(poolId);
+
+    //keep track of which pools have had their reward recipients set
+    s_rewardRecipientWeightsSet[poolId] = true;
+
+    //set the reward recipients, this will only be called once and contain the full set of RewardRecipients with a total weight of 100%
+    _setRewardRecipientWeights(poolId, rewardRecipientAndWeights, PERCENTAGE_SCALAR);
+
+    emit RewardRecipientsUpdated(poolId, rewardRecipientAndWeights);
+  }
+
+  function _setRewardRecipientWeights(
+    bytes32 poolId,
+    Common.AddressAndWeight[] calldata rewardRecipientAndWeights,
+    uint256 expectedWeight
+  ) internal {
+    //we can't update the weights if it contains duplicates
+    if (Common._hasDuplicateAddresses(rewardRecipientAndWeights)) revert InvalidAddress();
+
+    //loop all the reward recipients and validate the weight and address
+    uint256 totalWeight;
+    for (uint256 i; i < rewardRecipientAndWeights.length; ++i) {
+      //get the weight
+      uint256 recipientWeight = rewardRecipientAndWeights[i].weight;
+      //get the address
+      address recipientAddress = rewardRecipientAndWeights[i].addr;
+
+      //ensure the reward recipient address is not zero
+      if (recipientAddress == address(0)) revert InvalidAddress();
+
+      //save/overwrite the weight for the reward recipient
+      s_rewardRecipientWeights[poolId][recipientAddress] = recipientWeight;
+
+      unchecked {
+        //keep track of the cumulative weight, this cannot overflow as the total weight is restricted at 1e18
+        totalWeight += recipientWeight;
+      }
+    }
+
+    //if total weight is not met, the fees will either be under or over distributed
+    if (totalWeight != expectedWeight) revert InvalidWeights();
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function updateRewardRecipients(
+    bytes32 poolId,
+    Common.AddressAndWeight[] calldata newRewardRecipients
+  ) external override onlyOwner {
+    //create an array of poolIds to pass to _claimRewards if required
+    bytes32[] memory poolIds = new bytes32[](1);
+    poolIds[0] = poolId;
+
+    //loop all the reward recipients and claim their rewards before updating their weights
+    uint256 existingTotalWeight;
+    for (uint256 i; i < newRewardRecipients.length; ++i) {
+      //get the address
+      address recipientAddress = newRewardRecipients[i].addr;
+      //get the existing weight
+      uint256 existingWeight = s_rewardRecipientWeights[poolId][recipientAddress];
+
+      //if a recipient is updated, the rewards must be claimed first as they can't claim previous fees at the new weight
+      _claimRewards(newRewardRecipients[i].addr, poolIds);
+
+      unchecked {
+        //keep tally of the weights so that the expected collective weight is known
+        existingTotalWeight += existingWeight;
+      }
+    }
+
+    //update the reward recipients, if the new collective weight isn't equal to the previous collective weight, the fees will either be under or over distributed
+    _setRewardRecipientWeights(poolId, newRewardRecipients, existingTotalWeight);
+
+    //emit event
+    emit RewardRecipientsUpdated(poolId, newRewardRecipients);
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function payRecipients(bytes32 poolId, address[] calldata recipients) external onlyOwnerOrRecipientInPool(poolId) {
+    //convert poolIds to an array to match the interface of _claimRewards
+    bytes32[] memory poolIdsArray = new bytes32[](1);
+    poolIdsArray[0] = poolId;
+
+    //loop each recipient and claim the rewards for each of the pools and assets
+    for (uint256 i; i < recipients.length; ++i) {
+      _claimRewards(recipients[i], poolIdsArray);
+    }
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function addFeeManager(address newFeeManagerAddress) external onlyOwner {
+    if (newFeeManagerAddress == address(0)) revert InvalidAddress();
+    if (s_feeManagerAddressList[newFeeManagerAddress] != address(0)) revert InvalidAddress();
+
+    s_feeManagerAddressList[newFeeManagerAddress] = newFeeManagerAddress;
+
+    emit FeeManagerUpdated(newFeeManagerAddress);
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function removeFeeManager(address feeManagerAddress) external onlyOwner {
+    if (s_feeManagerAddressList[feeManagerAddress] == address(0)) revert InvalidAddress();
+    delete s_feeManagerAddressList[feeManagerAddress];
+  }
+
+  /// @inheritdoc IDestinationRewardManager
+  function getAvailableRewardPoolIds(
+    address recipient,
+    uint256 startIndex,
+    uint256 endIndex
+  ) external view returns (bytes32[] memory) {
+    //get the length of the pool ids which we will loop through and potentially return
+    uint256 registeredPoolIdsLength = s_registeredPoolIds.length;
+
+    uint256 lastIndex = endIndex > registeredPoolIdsLength ? registeredPoolIdsLength : endIndex;
+
+    if (startIndex > lastIndex) revert InvalidPoolLength();
+
+    //create a new array with the maximum amount of potential pool ids
+    bytes32[] memory claimablePoolIds = new bytes32[](lastIndex - startIndex);
+    //we want the pools which a recipient has funds for to be sequential, so we need to keep track of the index
+    uint256 poolIdArrayIndex;
+
+    //loop all the pool ids, and check if the recipient has a registered weight and a claimable amount
+    for (uint256 i = startIndex; i < lastIndex; ++i) {
+      //get the poolId
+      bytes32 poolId = s_registeredPoolIds[i];
+
+      //if the recipient has a weight, they are a recipient of this poolId
+      if (s_rewardRecipientWeights[poolId][recipient] != 0) {
+        //get the total in this pool
+        uint256 totalPoolAmount = s_totalRewardRecipientFees[poolId];
+        //if the recipient has any LINK, then add the poolId to the array
+        unchecked {
+          //s_totalRewardRecipientFeesLastClaimedAmounts can never exceed total pool amount, and the number of pools can't exceed the max array length
+          if (totalPoolAmount - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] != 0) {
+            claimablePoolIds[poolIdArrayIndex++] = poolId;
+          }
+        }
+      }
+    }
+
+    return claimablePoolIds;
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol
new file mode 100644
index 00000000000..52b2bd7c9a7
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
+import {IDestinationVerifier} from "./interfaces/IDestinationVerifier.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../libraries/Common.sol";
+import {IAccessController} from "../../shared/interfaces/IAccessController.sol";
+import {IDestinationVerifierProxy} from "./interfaces/IDestinationVerifierProxy.sol";
+import {IDestinationFeeManager} from "./interfaces/IDestinationFeeManager.sol";
+
+// OCR2 standard
+uint256 constant MAX_NUM_ORACLES = 31;
+
+/**
+ * @title DestinationVerifier
+ * @author Michael Fletcher
+ * @notice This contract will be used to verify reports based on the oracle signatures. This is not the source verifier which required individual fee configurations, instead, this checks that a report has been signed by one of the configured oracles.
+ */
+contract DestinationVerifier is IDestinationVerifier, ConfirmedOwner, TypeAndVersionInterface, IERC165 {
+  /// @notice The list of DON configurations by hash(address|donConfigId) - set to true if the signer is part of the config
+  mapping(bytes32 => bool) private s_signerByAddressAndDonConfigId;
+
+  /// array of DON configs
+  DonConfig[] private s_donConfigs;
+
+  /// @notice The address of the verifierProxy
+  address public s_feeManager;
+
+  /// @notice The address of the access controller
+  address public s_accessController;
+
+  /// @notice The address of the verifierProxy
+  IDestinationVerifierProxy public immutable i_verifierProxy;
+
+  /// @notice This error is thrown whenever trying to set a config
+  /// with a fault tolerance of 0
+  error FaultToleranceMustBePositive();
+
+  /// @notice This error is thrown whenever a report is signed
+  /// with more than the max number of signers
+  /// @param numSigners The number of signers who have signed the report
+  /// @param maxSigners The maximum number of signers that can sign a report
+  error ExcessSigners(uint256 numSigners, uint256 maxSigners);
+
+  /// @notice This error is thrown whenever a report is signed or expected to be signed with less than the minimum number of signers
+  /// @param numSigners The number of signers who have signed the report
+  /// @param minSigners The minimum number of signers that need to sign a report
+  error InsufficientSigners(uint256 numSigners, uint256 minSigners);
+
+  /// @notice This error is thrown whenever a report is submitted with no signatures
+  error NoSigners();
+
+  /// @notice This error is thrown whenever a DonConfig already exists
+  /// @param donConfigId The ID of the DonConfig that already exists
+  error DonConfigAlreadyExists(bytes24 donConfigId);
+
+  /// @notice This error is thrown whenever the R and S signer components
+  /// have different lengths
+  /// @param rsLength The number of r signature components
+  /// @param ssLength The number of s signature components
+  error MismatchedSignatures(uint256 rsLength, uint256 ssLength);
+
+  /// @notice This error is thrown whenever setting a config with duplicate signatures
+  error NonUniqueSignatures();
+
+  /* @notice This error is thrown whenever a report fails to verify. This error be thrown for multiple reasons and it's purposely like
+   * this to prevent information being leaked about the verification process which could be used to enable free verifications maliciously
+   */
+  error BadVerification();
+
+  /// @notice This error is thrown whenever a zero address is passed
+  error ZeroAddress();
+
+  /// @notice This error is thrown when the fee manager at an address does
+  /// not conform to the fee manager interface
+  error FeeManagerInvalid();
+
+  /// @notice This error is thrown whenever an address tries
+  /// to execute a verification that it is not authorized to do so
+  error AccessForbidden();
+
+  /// @notice This error is thrown whenever a config does not exist
+  error DonConfigDoesNotExist();
+
+  /// @notice this error is thrown when the verifierProxy is incorrect when initialising
+  error VerifierProxyInvalid();
+
+  /// @notice This error is thrown when the activation time is either in the future or less than the current configs
+  error BadActivationTime();
+
+  /// @notice This event is emitted when a new report is verified.
+  /// It is used to keep a historical record of verified reports.
+  event ReportVerified(bytes32 indexed feedId, address requester);
+
+  /// @notice This event is emitted whenever a configuration is activated or deactivated
+  event ConfigActivated(bytes24 donConfigId, bool isActive);
+
+  /// @notice This event is emitted whenever a configuration is removed
+  event ConfigRemoved(bytes24 donConfigId);
+
+  /// @notice event is emitted whenever a new DON Config is set
+  event ConfigSet(
+    bytes24 indexed donConfigId,
+    address[] signers,
+    uint8 f,
+    Common.AddressAndWeight[] recipientAddressesAndWeights
+  );
+
+  /// @notice This event is emitted when a new fee manager is set
+  /// @param oldFeeManager The old fee manager address
+  /// @param newFeeManager The new fee manager address
+  event FeeManagerSet(address oldFeeManager, address newFeeManager);
+
+  /// @notice This event is emitted when a new access controller is set
+  /// @param oldAccessController The old access controller address
+  /// @param newAccessController The new access controller address
+  event AccessControllerSet(address oldAccessController, address newAccessController);
+
+  struct DonConfig {
+    // The ID of the DonConfig
+    bytes24 donConfigId;
+    // Fault tolerance of the DON
+    uint8 f;
+    // Whether the config is active
+    bool isActive;
+    // The time the config was set
+    uint32 activationTime;
+  }
+
+  constructor(address verifierProxy) ConfirmedOwner(msg.sender) {
+    if (verifierProxy == address(0)) {
+      revert ZeroAddress();
+    }
+
+    i_verifierProxy = IDestinationVerifierProxy(verifierProxy);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function verify(
+    bytes calldata signedReport,
+    bytes calldata parameterPayload,
+    address sender
+  ) external payable override checkValidProxy checkAccess(sender) returns (bytes memory) {
+    (bytes memory verifierResponse, bytes32 donConfigId) = _verify(signedReport, sender);
+
+    address fm = s_feeManager;
+    if (fm != address(0)) {
+      //process the fee and catch the error
+      try IDestinationFeeManager(fm).processFee{value: msg.value}(donConfigId, signedReport, parameterPayload, sender) {
+        //do nothing
+      } catch {
+        // we purposefully obfuscate the error here to prevent information leaking leading to free verifications
+        revert BadVerification();
+      }
+    }
+
+    return verifierResponse;
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function verifyBulk(
+    bytes[] calldata signedReports,
+    bytes calldata parameterPayload,
+    address sender
+  ) external payable override checkValidProxy checkAccess(sender) returns (bytes[] memory) {
+    bytes[] memory verifierResponses = new bytes[](signedReports.length);
+    bytes32[] memory donConfigs = new bytes32[](signedReports.length);
+
+    for (uint256 i; i < signedReports.length; ++i) {
+      (bytes memory report, bytes32 config) = _verify(signedReports[i], sender);
+      verifierResponses[i] = report;
+      donConfigs[i] = config;
+    }
+
+    address fm = s_feeManager;
+    if (fm != address(0)) {
+      //process the fee and catch the error
+      try
+        IDestinationFeeManager(fm).processFeeBulk{value: msg.value}(donConfigs, signedReports, parameterPayload, sender)
+      {
+        //do nothing
+      } catch {
+        // we purposefully obfuscate the error here to prevent information leaking leading to free verifications
+        revert BadVerification();
+      }
+    }
+
+    return verifierResponses;
+  }
+
+  function _verify(bytes calldata signedReport, address sender) internal returns (bytes memory, bytes32) {
+    (
+      bytes32[3] memory reportContext,
+      bytes memory reportData,
+      bytes32[] memory rs,
+      bytes32[] memory ss,
+      bytes32 rawVs
+    ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32));
+
+    // Signature lengths must match
+    if (rs.length != ss.length) revert MismatchedSignatures(rs.length, ss.length);
+
+    //Must always be at least 1 signer
+    if (rs.length == 0) revert NoSigners();
+
+    // The payload is hashed and signed by the oracles - we need to recover the addresses
+    bytes32 signedPayload = keccak256(abi.encodePacked(keccak256(reportData), reportContext));
+    address[] memory signers = new address[](rs.length);
+    for (uint256 i; i < rs.length; ++i) {
+      signers[i] = ecrecover(signedPayload, uint8(rawVs[i]) + 27, rs[i], ss[i]);
+    }
+
+    // Duplicate signatures are not allowed
+    if (Common._hasDuplicateAddresses(signers)) {
+      revert BadVerification();
+    }
+
+    //We need to know the timestamp the report was generated to lookup the active activeDonConfig
+    uint256 reportTimestamp = _decodeReportTimestamp(reportData);
+
+    // Find the latest config for this report
+    DonConfig memory activeDonConfig = _findActiveConfig(reportTimestamp);
+
+    // Check a config has been set
+    if (activeDonConfig.donConfigId == bytes24(0)) {
+      revert BadVerification();
+    }
+
+    //check the config is active
+    if (!activeDonConfig.isActive) {
+      revert BadVerification();
+    }
+
+    //check we have enough signatures
+    if (signers.length <= activeDonConfig.f) {
+      revert BadVerification();
+    }
+
+    //check each signer is registered against the active DON
+    bytes32 signerDonConfigKey;
+    for (uint256 i; i < signers.length; ++i) {
+      signerDonConfigKey = keccak256(abi.encodePacked(signers[i], activeDonConfig.donConfigId));
+      if (!s_signerByAddressAndDonConfigId[signerDonConfigKey]) {
+        revert BadVerification();
+      }
+    }
+
+    emit ReportVerified(bytes32(reportData), sender);
+
+    return (reportData, activeDonConfig.donConfigId);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function setConfigWithActivationTime(
+    address[] memory signers,
+    uint8 f,
+    Common.AddressAndWeight[] memory recipientAddressesAndWeights,
+    uint32 activationTime
+  ) external override checkConfigValid(signers.length, f) onlyOwner {
+    _setConfig(signers, f, recipientAddressesAndWeights, activationTime);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function setConfig(
+    address[] memory signers,
+    uint8 f,
+    Common.AddressAndWeight[] memory recipientAddressesAndWeights
+  ) external override checkConfigValid(signers.length, f) onlyOwner {
+    _setConfig(signers, f, recipientAddressesAndWeights, uint32(block.timestamp));
+  }
+
+  function _setConfig(
+    address[] memory signers,
+    uint8 f,
+    Common.AddressAndWeight[] memory recipientAddressesAndWeights,
+    uint32 activationTime
+  ) internal checkConfigValid(signers.length, f) onlyOwner {
+    // Duplicate addresses would break protocol rules
+    if (Common._hasDuplicateAddresses(signers)) {
+      revert NonUniqueSignatures();
+    }
+
+    //activation time cannot be in the future
+    if (activationTime > block.timestamp) {
+      revert BadActivationTime();
+    }
+
+    // Sort signers to ensure donConfigId is deterministic
+    Common._quickSort(signers, 0, int256(signers.length - 1));
+
+    //DonConfig is made up of hash(signers|f)
+    bytes24 donConfigId = bytes24(keccak256(abi.encodePacked(signers, f)));
+
+    // Register the signers for this DON
+    for (uint256 i; i < signers.length; ++i) {
+      if (signers[i] == address(0)) revert ZeroAddress();
+      /** This index is registered so we can efficiently lookup whether a NOP is part of a config without having to
+                loop through the entire config each verification. It's effectively a DonConfig <-> Signer
+                composite key which keys track of all historic configs for a signer */
+      s_signerByAddressAndDonConfigId[keccak256(abi.encodePacked(signers[i], donConfigId))] = true;
+    }
+
+    // Check the activation time is greater than the latest config
+    uint256 donConfigLength = s_donConfigs.length;
+    if (donConfigLength > 0 && s_donConfigs[donConfigLength - 1].activationTime > activationTime) {
+      revert BadActivationTime();
+    }
+
+    // Check the config we're setting isn't already set as the current active config as this will increase search costs unnecessarily when verifying historic reports
+    if (donConfigLength > 0 && s_donConfigs[donConfigLength - 1].donConfigId == donConfigId) {
+      revert DonConfigAlreadyExists(donConfigId);
+    }
+
+    // We may want to register these later or skip this step in the unlikely scenario they've previously been registered in the RewardsManager
+    if (recipientAddressesAndWeights.length != 0) {
+      IDestinationFeeManager(s_feeManager).setFeeRecipients(donConfigId, recipientAddressesAndWeights);
+    }
+
+    // push the DonConfig
+    s_donConfigs.push(DonConfig(donConfigId, f, true, activationTime));
+
+    emit ConfigSet(donConfigId, signers, f, recipientAddressesAndWeights);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function setFeeManager(address feeManager) external override onlyOwner {
+    if (
+      !IERC165(feeManager).supportsInterface(IDestinationFeeManager.processFee.selector) ||
+      !IERC165(feeManager).supportsInterface(IDestinationFeeManager.processFeeBulk.selector) ||
+      !IERC165(feeManager).supportsInterface(IDestinationFeeManager.setFeeRecipients.selector)
+    ) revert FeeManagerInvalid();
+
+    address oldFeeManager = s_feeManager;
+    s_feeManager = feeManager;
+
+    emit FeeManagerSet(oldFeeManager, feeManager);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function setAccessController(address accessController) external override onlyOwner {
+    address oldAccessController = s_accessController;
+    s_accessController = accessController;
+    emit AccessControllerSet(oldAccessController, accessController);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function setConfigActive(uint256 donConfigIndex, bool isActive) external onlyOwner {
+    // Config must exist
+    if (donConfigIndex >= s_donConfigs.length) {
+      revert DonConfigDoesNotExist();
+    }
+
+    // Update the config
+    DonConfig storage config = s_donConfigs[donConfigIndex];
+    config.isActive = isActive;
+
+    emit ConfigActivated(config.donConfigId, isActive);
+  }
+
+  /// @inheritdoc IDestinationVerifier
+  function removeLatestConfig() external onlyOwner {
+    if (s_donConfigs.length == 0) {
+      revert DonConfigDoesNotExist();
+    }
+
+    DonConfig memory config = s_donConfigs[s_donConfigs.length - 1];
+
+    s_donConfigs.pop();
+
+    emit ConfigRemoved(config.donConfigId);
+  }
+
+  function _decodeReportTimestamp(bytes memory reportPayload) internal pure returns (uint256) {
+    (, , uint256 timestamp) = abi.decode(reportPayload, (bytes32, uint32, uint32));
+
+    return timestamp;
+  }
+
+  function _findActiveConfig(uint256 timestamp) internal view returns (DonConfig memory) {
+    DonConfig memory activeDonConfig;
+
+    // 99% of the time the signer config will be the last index, however for historic reports generated by a previous configuration we'll need to cycle back
+    uint256 i = s_donConfigs.length;
+    while (i > 0) {
+      --i;
+      if (s_donConfigs[i].activationTime <= timestamp) {
+        activeDonConfig = s_donConfigs[i];
+        break;
+      }
+    }
+    return activeDonConfig;
+  }
+
+  modifier checkConfigValid(uint256 numSigners, uint256 f) {
+    if (f == 0) revert FaultToleranceMustBePositive();
+    if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES);
+    if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1);
+    _;
+  }
+
+  modifier checkValidProxy() {
+    if (address(i_verifierProxy) != msg.sender) {
+      revert AccessForbidden();
+    }
+    _;
+  }
+
+  modifier checkAccess(address sender) {
+    address ac = s_accessController;
+    if (address(ac) != address(0) && !IAccessController(ac).hasAccess(sender, msg.data)) revert AccessForbidden();
+    _;
+  }
+
+  /// @inheritdoc IERC165
+  function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
+    return
+      interfaceId == this.verify.selector ||
+      interfaceId == this.verifyBulk.selector ||
+      interfaceId == this.s_accessController.selector ||
+      interfaceId == this.s_feeManager.selector ||
+      interfaceId == this.setConfig.selector ||
+      interfaceId == this.setConfigWithActivationTime.selector ||
+      interfaceId == this.setFeeManager.selector ||
+      interfaceId == this.setAccessController.selector ||
+      interfaceId == this.setConfigActive.selector;
+  }
+
+  /// @inheritdoc TypeAndVersionInterface
+  function typeAndVersion() external pure override returns (string memory) {
+    return "DestinationVerifier 1.0.0";
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol
new file mode 100644
index 00000000000..1a5c62b4292
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
+import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
+import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {IDestinationVerifierProxy} from "./interfaces/IDestinationVerifierProxy.sol";
+import {IDestinationVerifier} from "./interfaces/IDestinationVerifier.sol";
+
+/**
+ * @title DestinationVerifierProxy
+ * @author Michael Fletcher
+ * @notice This contract will be used to route all requests through to the assigned verifier contract. This contract does not support individual feed configurations and is aimed at being a simple proxy for the verifier contract on any destination chain.
+ */
+contract DestinationVerifierProxy is IDestinationVerifierProxy, ConfirmedOwner, TypeAndVersionInterface, IERC165 {
+  /// @notice The active verifier for this proxy
+  IDestinationVerifier private s_verifier;
+
+  /// @notice This error is thrown whenever a zero address is passed
+  error ZeroAddress();
+
+  /// @notice This error is thrown when trying to set a verifier address that does not implement the expected interface
+  error VerifierInvalid(address verifierAddress);
+
+  constructor() ConfirmedOwner(msg.sender) {}
+
+  /// @inheritdoc TypeAndVersionInterface
+  function typeAndVersion() external pure override returns (string memory) {
+    return "DestinationVerifierProxy 1.0.0";
+  }
+
+  /// @inheritdoc IDestinationVerifierProxy
+  function verify(bytes calldata payload, bytes calldata parameterPayload) external payable returns (bytes memory) {
+    return s_verifier.verify{value: msg.value}(payload, parameterPayload, msg.sender);
+  }
+
+  /// @inheritdoc IDestinationVerifierProxy
+  function verifyBulk(
+    bytes[] calldata payloads,
+    bytes calldata parameterPayload
+  ) external payable returns (bytes[] memory verifiedReports) {
+    return s_verifier.verifyBulk{value: msg.value}(payloads, parameterPayload, msg.sender);
+  }
+
+  /// @inheritdoc IDestinationVerifierProxy
+  function setVerifier(address verifierAddress) external onlyOwner {
+    //check it supports the functions we need
+    if (
+      !IERC165(verifierAddress).supportsInterface(IDestinationVerifier.s_accessController.selector) ||
+      !IERC165(verifierAddress).supportsInterface(IDestinationVerifier.s_feeManager.selector) ||
+      !IERC165(verifierAddress).supportsInterface(IDestinationVerifier.verify.selector) ||
+      !IERC165(verifierAddress).supportsInterface(IDestinationVerifier.verifyBulk.selector)
+    ) revert VerifierInvalid(verifierAddress);
+
+    s_verifier = IDestinationVerifier(verifierAddress);
+  }
+
+  /// @inheritdoc IDestinationVerifierProxy
+  // solhint-disable-next-line func-name-mixedcase
+  function s_feeManager() external view override returns (address) {
+    return s_verifier.s_feeManager();
+  }
+
+  /// @inheritdoc IDestinationVerifierProxy
+  // solhint-disable-next-line func-name-mixedcase
+  function s_accessController() external view override returns (address) {
+    return s_verifier.s_accessController();
+  }
+
+  /// @inheritdoc IERC165
+  function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
+    return
+      interfaceId == this.setVerifier.selector ||
+      interfaceId == this.verify.selector ||
+      interfaceId == this.verifyBulk.selector ||
+      interfaceId == this.s_feeManager.selector ||
+      interfaceId == this.s_accessController.selector;
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol
new file mode 100644
index 00000000000..f92e7cd146b
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
+
+interface IDestinationFeeManager is IERC165 {
+  /**
+   * @notice Calculate the applied fee and the reward from a report. If the sender is a subscriber, they will receive a discount.
+   * @param subscriber address trying to verify
+   * @param report report to calculate the fee for
+   * @param quoteAddress address of the quote payment token
+   * @return (fee, reward, totalDiscount) fee and the reward data with the discount applied
+   */
+  function getFeeAndReward(
+    address subscriber,
+    bytes memory report,
+    address quoteAddress
+  ) external returns (Common.Asset memory, Common.Asset memory, uint256);
+
+  /**
+   * @notice Sets the native surcharge
+   * @param surcharge surcharge to be paid if paying in native
+   */
+  function setNativeSurcharge(uint64 surcharge) external;
+
+  /**
+   * @notice Adds a subscriber to the fee manager
+   * @param subscriber address of the subscriber
+   * @param feedId feed id to apply the discount to
+   * @param token token to apply the discount to
+   * @param discount discount to be applied to the fee
+   */
+  function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external;
+
+  /**
+   * @notice Withdraws any native or LINK rewards to the owner address
+   * @param assetAddress address of the asset to withdraw
+   * @param recipientAddress address to withdraw to
+   * @param quantity quantity to withdraw
+   */
+  function withdraw(address assetAddress, address recipientAddress, uint192 quantity) external;
+
+  /**
+   * @notice Returns the link balance of the fee manager
+   * @return link balance of the fee manager
+   */
+  function linkAvailableForPayment() external returns (uint256);
+
+  /**
+   * @notice Admin function to pay the LINK deficit for a given config digest
+   * @param configDigest the config digest to pay the deficit for
+   */
+  function payLinkDeficit(bytes32 configDigest) external;
+
+  /**
+   * @notice Adds the verifier to the list of verifiers able to use the feeManager
+   * @param verifier address of the verifier
+   */
+  function addVerifier(address verifier) external;
+
+  /**
+   * @notice Removes the verifier from the list of verifiers able to use the feeManager
+   * @param verifier address of the verifier
+   */
+  function removeVerifier(address verifier) external;
+
+  /**
+   * @notice Sets the reward manager to the address
+   * @param rewardManager address of the reward manager
+   */
+  function setRewardManager(address rewardManager) external;
+
+  /**
+   * @notice Handles fees for a report from the subscriber and manages rewards
+   * @param poolId pool id of the pool to pay into
+   * @param payload report to process the fee for
+   * @param parameterPayload fee payload
+   * @param subscriber address of the fee will be applied
+   */
+  function processFee(
+    bytes32 poolId,
+    bytes calldata payload,
+    bytes calldata parameterPayload,
+    address subscriber
+  ) external payable;
+
+  /**
+   * @notice Processes the fees for each report in the payload, billing the subscriber and paying the reward manager
+   * @param poolIds pool ids of the pool to pay into
+   * @param payloads reports to process
+   * @param parameterPayload fee payload
+   * @param subscriber address of the user to process fee for
+   */
+  function processFeeBulk(
+    bytes32[] memory poolIds,
+    bytes[] calldata payloads,
+    bytes calldata parameterPayload,
+    address subscriber
+  ) external payable;
+
+  /**
+   * @notice Sets the fee recipients according to the fee manager
+   * @param configDigest digest of the configuration
+   * @param rewardRecipientAndWeights the address and weights of all the recipients to receive rewards
+   */
+  function setFeeRecipients(
+    bytes32 configDigest,
+    Common.AddressAndWeight[] calldata rewardRecipientAndWeights
+  ) external;
+
+  /**
+   * @notice The structure to hold a fee and reward to verify a report
+   * @param digest the digest linked to the fee and reward
+   * @param fee the fee paid to verify the report
+   * @param reward the reward paid upon verification
+   & @param appliedDiscount the discount applied to the reward
+   */
+  struct FeeAndReward {
+    bytes32 configDigest;
+    Common.Asset fee;
+    Common.Asset reward;
+    uint256 appliedDiscount;
+  }
+
+  /**
+   * @notice The structure to hold quote metadata
+   * @param quoteAddress the address of the quote
+   */
+  struct Quote {
+    address quoteAddress;
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol
new file mode 100644
index 00000000000..95f07937aec
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {Common} from "../../libraries/Common.sol";
+
+interface IDestinationRewardManager is IERC165 {
+  /**
+   * @notice Record the fee received for a particular pool
+   * @param payments array of structs containing pool id and amount
+   * @param payee the user the funds should be retrieved from
+   */
+  function onFeePaid(FeePayment[] calldata payments, address payee) external;
+
+  /**
+   * @notice Claims the rewards in a specific pool
+   * @param poolIds array of poolIds to claim rewards for
+   */
+  function claimRewards(bytes32[] calldata poolIds) external;
+
+  /**
+   * @notice Set the RewardRecipients and weights for a specific pool. This should only be called once per pool Id. Else updateRewardRecipients should be used.
+   * @param poolId poolId to set RewardRecipients and weights for
+   * @param rewardRecipientAndWeights array of each RewardRecipient and associated weight
+   */
+  function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata rewardRecipientAndWeights) external;
+
+  /**
+   * @notice Updates a subset the reward recipients for a specific poolId. The collective weight of the recipients should add up to the recipients existing weights. Any recipients with a weight of 0 will be removed.
+   * @param poolId the poolId to update
+   * @param newRewardRecipients array of new reward recipients
+   */
+  function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata newRewardRecipients) external;
+
+  /**
+   * @notice Pays all the recipients for each of the pool ids
+   * @param poolId the pool id to pay recipients for
+   * @param recipients array of recipients to pay within the pool
+   */
+  function payRecipients(bytes32 poolId, address[] calldata recipients) external;
+
+  /**
+   * @notice Add the fee manager to the list of feeManagers able to call the reward manager
+   * @param newFeeManager address of the new verifier proxy
+   */
+  function addFeeManager(address newFeeManager) external;
+
+  /**
+   * @notice Removes the fee manager. This needs to be done post construction to prevent a circular dependency.
+   * @param feeManager address of the verifier proxy to remove
+   */
+  function removeFeeManager(address feeManager) external;
+
+  /**
+   * @notice Gets a list of pool ids which have reward for a specific recipient.
+   * @param recipient address of the recipient to get pool ids for
+   * @param startIndex the index to start from
+   * @param endIndex the index to stop at
+   */
+  function getAvailableRewardPoolIds(
+    address recipient,
+    uint256 startIndex,
+    uint256 endIndex
+  ) external view returns (bytes32[] memory);
+
+  /**
+   * @notice The structure to hold a fee payment notice
+   * @param poolId the poolId receiving the payment
+   * @param amount the amount being paid
+   */
+  struct FeePayment {
+    bytes32 poolId;
+    uint192 amount;
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol
new file mode 100644
index 00000000000..69516f6e924
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {Common} from "../../libraries/Common.sol";
+
+interface IDestinationVerifier {
+  /**
+   * @notice Verifies that the data encoded has been signed correctly using the signatures included within the payload.
+   * @param signedReport The encoded data to be verified.
+   * @param parameterPayload The encoded parameters to be used in the verification and billing process.
+   * @param sender The address that requested to verify the contract.Used for logging and applying the fee.
+   * @dev Verification is typically only done through the proxy contract so we can't just use msg.sender.
+   * @return verifierResponse The encoded verified response.
+   */
+  function verify(
+    bytes calldata signedReport,
+    bytes calldata parameterPayload,
+    address sender
+  ) external payable returns (bytes memory verifierResponse);
+
+  /**
+   * @notice Bulk verifies that the data encoded has been signed correctly using the signatures included within the payload.
+   * @param signedReports The encoded data to be verified.
+   * @param parameterPayload The encoded parameters to be used in the verification and billing process.
+   * @param sender The address that requested to verify the contract. Used for logging and applying the fee.
+   * @dev Verification is typically only done through the proxy contract so we can't just use msg.sender.
+   * @return verifiedReports The encoded verified responses.
+   */
+  function verifyBulk(
+    bytes[] calldata signedReports,
+    bytes calldata parameterPayload,
+    address sender
+  ) external payable returns (bytes[] memory verifiedReports);
+
+  /**
+   * @notice sets off-chain reporting protocol configuration incl. participating oracles
+   * @param signers addresses with which oracles sign the reports
+   * @param f number of faulty oracles the system can tolerate
+   * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards
+   */
+  function setConfig(
+    address[] memory signers,
+    uint8 f,
+    Common.AddressAndWeight[] memory recipientAddressesAndWeights
+  ) external;
+
+  /**
+   * @notice sets off-chain reporting protocol configuration incl. participating oracles
+   * @param signers addresses with which oracles sign the reports
+   * @param f number of faulty oracles the system can tolerate
+   * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards
+   * @param activationTime the time at which the config was activated
+   */
+  function setConfigWithActivationTime(
+    address[] memory signers,
+    uint8 f,
+    Common.AddressAndWeight[] memory recipientAddressesAndWeights,
+    uint32 activationTime
+  ) external;
+
+  /**
+   * @notice Sets the fee manager address
+   * @param feeManager The address of the fee manager
+   */
+  function setFeeManager(address feeManager) external;
+
+  /**
+   * @notice Sets the access controller address
+   * @param accessController The address of the access controller
+   */
+  function setAccessController(address accessController) external;
+
+  /**
+   * @notice Updates the config active status
+   * @param donConfigId The ID of the config to update
+   * @param isActive The new config active status
+   */
+  function setConfigActive(uint256 donConfigId, bool isActive) external;
+
+  /**
+   * @notice Removes the latest config
+   */
+  function removeLatestConfig() external;
+
+  /*
+   * @notice Returns the reward manager
+   * @return IDestinationRewardManager
+   */
+  // solhint-disable-next-line func-name-mixedcase
+  function s_feeManager() external view returns (address);
+
+  /**
+   * @notice Returns the access controller
+   * @return IDestinationFeeManager
+   */
+  // solhint-disable-next-line func-name-mixedcase
+  function s_accessController() external view returns (address);
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol
new file mode 100644
index 00000000000..a88349b3014
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+interface IDestinationVerifierProxy {
+  /**
+   * @notice Verifies that the data encoded has been signed
+   * correctly by routing to the verifier, and bills the user if applicable.
+   * @param payload The encoded data to be verified, including the signed
+   * report.
+   * @param parameterPayload fee metadata for billing
+   * @return verifierResponse The encoded report from the verifier.
+   */
+  function verify(
+    bytes calldata payload,
+    bytes calldata parameterPayload
+  ) external payable returns (bytes memory verifierResponse);
+
+  /**
+   * @notice Bulk verifies that the data encoded has been signed
+   * correctly by routing to the correct verifier, and bills the user if applicable.
+   * @param payloads The encoded payloads to be verified, including the signed
+   * report.
+   * @param parameterPayload fee metadata for billing
+   * @return verifiedReports The encoded reports from the verifier.
+   */
+  function verifyBulk(
+    bytes[] calldata payloads,
+    bytes calldata parameterPayload
+  ) external payable returns (bytes[] memory verifiedReports);
+
+  /**
+   * @notice Sets the active verifier for this proxy
+   * @param verifierAddress The address of the verifier contract
+   */
+  function setVerifier(address verifierAddress) external;
+
+  /**
+   * @notice Used to honor the source verifierProxy feeManager interface
+   * @return IVerifierFeeManager
+   */
+  // solhint-disable-next-line func-name-mixedcase
+  function s_feeManager() external view returns (address);
+
+  /**
+   * @notice Used to honor the source verifierProxy feeManager interface
+   * @return AccessControllerInterface
+   */
+  // solhint-disable-next-line func-name-mixedcase
+  function s_accessController() external view returns (address);
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol
new file mode 100644
index 00000000000..8b70e5b2b33
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Test} from "forge-std/Test.sol";
+import {DestinationFeeManager} from "../../DestinationFeeManager.sol";
+import {DestinationRewardManager} from "../../DestinationRewardManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+import {DestinationFeeManagerProxy} from "../mocks/DestinationFeeManagerProxy.sol";
+
+/**
+ * @title BaseDestinationFeeManagerTest
+ * @author Michael Fletcher
+ * @notice Base class for all feeManager tests
+ * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup the feeManager
+ */
+contract BaseDestinationFeeManagerTest is Test {
+  //contracts
+  DestinationFeeManager internal feeManager;
+  DestinationRewardManager internal rewardManager;
+  DestinationFeeManagerProxy internal feeManagerProxy;
+
+  ERC20Mock internal link;
+  WERC20Mock internal native;
+
+  //erc20 config
+  uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether;
+  uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether;
+
+  //contract owner
+  address internal constant INVALID_ADDRESS = address(0);
+  address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN"))));
+  address internal constant USER = address(uint160(uint256(keccak256("USER"))));
+  address internal constant PROXY = address(uint160(uint256(keccak256("PROXY"))));
+
+  //version masks
+  bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
+  bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000;
+  bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000;
+  bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000;
+
+  //feed ids & config digests
+  bytes32 internal constant DEFAULT_FEED_1_V1 = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK;
+  bytes32 internal constant DEFAULT_FEED_1_V2 = (keccak256("ETH-USD") & V_MASK) | V2_BITMASK;
+  bytes32 internal constant DEFAULT_FEED_1_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK;
+
+  bytes32 internal constant DEFAULT_FEED_2_V3 = (keccak256("LINK-USD") & V_MASK) | V3_BITMASK;
+  bytes32 internal constant DEFAULT_CONFIG_DIGEST = keccak256("DEFAULT_CONFIG_DIGEST");
+  bytes32 internal constant DEFAULT_CONFIG_DIGEST2 = keccak256("DEFAULT_CONFIG_DIGEST2");
+
+  //report
+  uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10;
+  uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12;
+
+  //rewards
+  uint64 internal constant FEE_SCALAR = 1e18;
+
+  address internal constant NATIVE_WITHDRAW_ADDRESS = address(0);
+
+  //the selector for each error
+  bytes4 internal immutable INVALID_DISCOUNT_ERROR = DestinationFeeManager.InvalidDiscount.selector;
+  bytes4 internal immutable INVALID_ADDRESS_ERROR = DestinationFeeManager.InvalidAddress.selector;
+  bytes4 internal immutable INVALID_SURCHARGE_ERROR = DestinationFeeManager.InvalidSurcharge.selector;
+  bytes4 internal immutable EXPIRED_REPORT_ERROR = DestinationFeeManager.ExpiredReport.selector;
+  bytes4 internal immutable INVALID_DEPOSIT_ERROR = DestinationFeeManager.InvalidDeposit.selector;
+  bytes4 internal immutable INVALID_QUOTE_ERROR = DestinationFeeManager.InvalidQuote.selector;
+  bytes4 internal immutable UNAUTHORIZED_ERROR = DestinationFeeManager.Unauthorized.selector;
+  bytes4 internal immutable POOLID_MISMATCH_ERROR = DestinationFeeManager.PoolIdMismatch.selector;
+  bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner";
+  bytes internal constant INSUFFICIENT_ALLOWANCE_ERROR = "ERC20: insufficient allowance";
+  bytes4 internal immutable ZERO_DEFICIT = DestinationFeeManager.ZeroDeficit.selector;
+
+  //events emitted
+  event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount);
+  event NativeSurchargeUpdated(uint64 newSurcharge);
+  event InsufficientLink(IDestinationRewardManager.FeePayment[] feesAndRewards);
+  event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity);
+  event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity);
+  event DiscountApplied(
+    bytes32 indexed configDigest,
+    address indexed subscriber,
+    Common.Asset fee,
+    Common.Asset reward,
+    uint256 appliedDiscountQuantity
+  );
+
+  function setUp() public virtual {
+    //change to admin user
+    vm.startPrank(ADMIN);
+
+    //init required contracts
+    _initializeContracts();
+  }
+
+  function _initializeContracts() internal {
+    link = new ERC20Mock("LINK", "LINK", ADMIN, 0);
+    native = new WERC20Mock();
+
+    feeManagerProxy = new DestinationFeeManagerProxy();
+    rewardManager = new DestinationRewardManager(address(link));
+    feeManager = new DestinationFeeManager(
+      address(link),
+      address(native),
+      address(feeManagerProxy),
+      address(rewardManager)
+    );
+
+    //link the feeManager to the proxy
+    feeManagerProxy.setDestinationFeeManager(feeManager);
+
+    //link the feeManager to the reward manager
+    rewardManager.addFeeManager(address(feeManager));
+
+    //mint some tokens to the admin
+    link.mint(ADMIN, DEFAULT_LINK_MINT_QUANTITY);
+    native.mint(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY);
+    vm.deal(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY);
+
+    //mint some tokens to the user
+    link.mint(USER, DEFAULT_LINK_MINT_QUANTITY);
+    native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+    vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+
+    //mint some tokens to the proxy
+    link.mint(PROXY, DEFAULT_LINK_MINT_QUANTITY);
+    native.mint(PROXY, DEFAULT_NATIVE_MINT_QUANTITY);
+    vm.deal(PROXY, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function setSubscriberDiscount(
+    address subscriber,
+    bytes32 feedId,
+    address token,
+    uint256 discount,
+    address sender
+  ) internal {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //set the discount
+    feeManager.updateSubscriberDiscount(subscriber, feedId, token, uint64(discount));
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function setNativeSurcharge(uint256 surcharge, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //set the surcharge
+    feeManager.setNativeSurcharge(uint64(surcharge));
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  // solium-disable-next-line no-unused-vars
+  function getFee(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) {
+    //get the fee
+    (Common.Asset memory fee, , ) = feeManager.getFeeAndReward(subscriber, report, quote);
+
+    return fee;
+  }
+
+  function getReward(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) {
+    //get the reward
+    (, Common.Asset memory reward, ) = feeManager.getFeeAndReward(subscriber, report, quote);
+
+    return reward;
+  }
+
+  function getAppliedDiscount(bytes memory report, address quote, address subscriber) public view returns (uint256) {
+    //get the reward
+    (, , uint256 appliedDiscount) = feeManager.getFeeAndReward(subscriber, report, quote);
+
+    return appliedDiscount;
+  }
+
+  function getV1Report(bytes32 feedId) public pure returns (bytes memory) {
+    return abi.encode(feedId, uint32(0), int192(0), int192(0), int192(0), uint64(0), bytes32(0), uint64(0), uint64(0));
+  }
+
+  function getV2Report(bytes32 feedId) public view returns (bytes memory) {
+    return
+      abi.encode(
+        feedId,
+        uint32(0),
+        uint32(0),
+        uint192(DEFAULT_REPORT_NATIVE_FEE),
+        uint192(DEFAULT_REPORT_LINK_FEE),
+        uint32(block.timestamp),
+        int192(0)
+      );
+  }
+
+  function getV3Report(bytes32 feedId) public view returns (bytes memory) {
+    return
+      abi.encode(
+        feedId,
+        uint32(0),
+        uint32(0),
+        uint192(DEFAULT_REPORT_NATIVE_FEE),
+        uint192(DEFAULT_REPORT_LINK_FEE),
+        uint32(block.timestamp),
+        int192(0),
+        int192(0),
+        int192(0)
+      );
+  }
+
+  function getV3ReportWithCustomExpiryAndFee(
+    bytes32 feedId,
+    uint256 expiry,
+    uint256 linkFee,
+    uint256 nativeFee
+  ) public pure returns (bytes memory) {
+    return
+      abi.encode(
+        feedId,
+        uint32(0),
+        uint32(0),
+        uint192(nativeFee),
+        uint192(linkFee),
+        uint32(expiry),
+        int192(0),
+        int192(0),
+        int192(0)
+      );
+  }
+
+  function getLinkQuote() public view returns (address) {
+    return address(link);
+  }
+
+  function getNativeQuote() public view returns (address) {
+    return address(native);
+  }
+
+  function withdraw(address assetAddress, address recipient, uint256 amount, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //set the surcharge
+    feeManager.withdraw(assetAddress, recipient, uint192(amount));
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function getLinkBalance(address balanceAddress) public view returns (uint256) {
+    return link.balanceOf(balanceAddress);
+  }
+
+  function getNativeBalance(address balanceAddress) public view returns (uint256) {
+    return native.balanceOf(balanceAddress);
+  }
+
+  function getNativeUnwrappedBalance(address balanceAddress) public view returns (uint256) {
+    return balanceAddress.balance;
+  }
+
+  function mintLink(address recipient, uint256 amount) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(ADMIN);
+
+    //mint the link to the recipient
+    link.mint(recipient, amount);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function mintNative(address recipient, uint256 amount, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //mint the native to the recipient
+    native.mint(recipient, amount);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function issueUnwrappedNative(address recipient, uint256 quantity) public {
+    vm.deal(recipient, quantity);
+  }
+
+  function ProcessFeeAsUser(
+    bytes32 poolId,
+    bytes memory payload,
+    address subscriber,
+    address tokenAddress,
+    uint256 wrappedNativeValue,
+    address sender
+  ) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //process the fee
+    feeManager.processFee{value: wrappedNativeValue}(poolId, payload, abi.encode(tokenAddress), subscriber);
+
+    //change ProcessFeeAsUserback to the original address
+    changePrank(originalAddr);
+  }
+
+  function processFee(
+    bytes32 poolId,
+    bytes memory payload,
+    address subscriber,
+    address feeAddress,
+    uint256 wrappedNativeValue
+  ) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(subscriber);
+
+    //process the fee
+    feeManagerProxy.processFee{value: wrappedNativeValue}(poolId, payload, abi.encode(feeAddress));
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function processFee(
+    bytes32[] memory poolIds,
+    bytes[] memory payloads,
+    address subscriber,
+    address feeAddress,
+    uint256 wrappedNativeValue
+  ) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(subscriber);
+
+    //process the fee
+    feeManagerProxy.processFeeBulk{value: wrappedNativeValue}(poolIds, payloads, abi.encode(feeAddress));
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function getPayload(bytes memory reportPayload) public pure returns (bytes memory) {
+    return abi.encode([DEFAULT_CONFIG_DIGEST, 0, 0], reportPayload, new bytes32[](1), new bytes32[](1), bytes32(""));
+  }
+
+  function approveLink(address spender, uint256 quantity, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //approve the link to be transferred
+    link.approve(spender, quantity);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function approveNative(address spender, uint256 quantity, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //approve the link to be transferred
+    native.approve(spender, quantity);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function payLinkDeficit(bytes32 configDigest, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //approve the link to be transferred
+    feeManager.payLinkDeficit(configDigest);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function getLinkDeficit(bytes32 configDigest) public view returns (uint256) {
+    return feeManager.s_linkDeficit(configDigest);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol
new file mode 100644
index 00000000000..305125c3329
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import "./BaseDestinationFeeManager.t.sol";
+
+/**
+ * @title BaseDestinationFeeManagerTest
+ * @author Michael Fletcher
+ * @notice This contract will test the setup functionality of the feemanager
+ */
+contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest {
+  function setUp() public override {
+    super.setUp();
+  }
+
+  function test_WithdrawERC20() public {
+    //simulate a fee
+    mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY);
+
+    //get the balances to ne used for comparison
+    uint256 contractBalance = getLinkBalance(address(feeManager));
+    uint256 adminBalance = getLinkBalance(ADMIN);
+
+    //the amount to withdraw
+    uint256 withdrawAmount = contractBalance / 2;
+
+    //withdraw some balance
+    withdraw(address(link), ADMIN, withdrawAmount, ADMIN);
+
+    //check the balance has been reduced
+    uint256 newContractBalance = getLinkBalance(address(feeManager));
+    uint256 newAdminBalance = getLinkBalance(ADMIN);
+
+    //check the balance is greater than zero
+    assertGt(newContractBalance, 0);
+    //check the balance has been reduced by the correct amount
+    assertEq(newContractBalance, contractBalance - withdrawAmount);
+    //check the admin balance has increased by the correct amount
+    assertEq(newAdminBalance, adminBalance + withdrawAmount);
+  }
+
+  function test_WithdrawUnwrappedNative() public {
+    //issue funds straight to the contract to bypass the lack of fallback function
+    issueUnwrappedNative(address(feeManager), DEFAULT_NATIVE_MINT_QUANTITY);
+
+    //get the balances to be used for comparison
+    uint256 contractBalance = getNativeUnwrappedBalance(address(feeManager));
+    uint256 adminBalance = getNativeUnwrappedBalance(ADMIN);
+
+    //the amount to withdraw
+    uint256 withdrawAmount = contractBalance / 2;
+
+    //withdraw some balance
+    withdraw(NATIVE_WITHDRAW_ADDRESS, ADMIN, withdrawAmount, ADMIN);
+
+    //check the balance has been reduced
+    uint256 newContractBalance = getNativeUnwrappedBalance(address(feeManager));
+    uint256 newAdminBalance = getNativeUnwrappedBalance(ADMIN);
+
+    //check the balance is greater than zero
+    assertGt(newContractBalance, 0);
+    //check the balance has been reduced by the correct amount
+    assertEq(newContractBalance, contractBalance - withdrawAmount);
+    //check the admin balance has increased by the correct amount
+    assertEq(newAdminBalance, adminBalance + withdrawAmount);
+  }
+
+  function test_WithdrawNonAdminAddr() public {
+    //simulate a fee
+    mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY);
+
+    //should revert if not admin
+    vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR);
+
+    //withdraw some balance
+    withdraw(address(link), ADMIN, DEFAULT_LINK_MINT_QUANTITY, USER);
+  }
+
+  function test_eventIsEmittedAfterSurchargeIsSet() public {
+    //native surcharge
+    uint64 nativeSurcharge = FEE_SCALAR / 5;
+
+    //expect an emit
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit NativeSurchargeUpdated(nativeSurcharge);
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+  }
+
+  function test_subscriberDiscountEventIsEmittedOnUpdate() public {
+    //native surcharge
+    uint64 discount = FEE_SCALAR / 3;
+
+    //an event should be emitted
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit SubscriberDiscountUpdated(USER, DEFAULT_FEED_1_V3, address(native), discount);
+
+    //set the surcharge
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN);
+  }
+
+  function test_eventIsEmittedUponWithdraw() public {
+    //simulate a fee
+    mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY);
+
+    //the amount to withdraw
+    uint192 withdrawAmount = 1;
+
+    //expect an emit
+    vm.expectEmit();
+
+    //the event to be emitted
+    emit Withdraw(ADMIN, ADMIN, address(link), withdrawAmount);
+
+    //withdraw some balance
+    withdraw(address(link), ADMIN, withdrawAmount, ADMIN);
+  }
+
+  function test_linkAvailableForPaymentReturnsLinkBalance() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //check there's a balance
+    assertGt(getLinkBalance(address(feeManager)), 0);
+
+    //check the link available for payment is the link balance
+    assertEq(feeManager.linkAvailableForPayment(), getLinkBalance(address(feeManager)));
+  }
+
+  function test_payLinkDeficit() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3));
+
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //not enough funds in the reward pool should trigger an insufficient link event
+    vm.expectEmit();
+
+    IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1);
+    contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+
+    emit InsufficientLink(contractFees);
+
+    //process the fee
+    processFee(contractFees[0].poolId, payload, USER, address(native), 0);
+
+    //double check the rewardManager balance is 0
+    assertEq(getLinkBalance(address(rewardManager)), 0);
+
+    //simulate a deposit of link to cover the deficit
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    vm.expectEmit();
+    emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE);
+
+    //pay the deficit which will transfer link from the rewardManager to the rewardManager
+    payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN);
+
+    //check the rewardManager received the link
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_payLinkDeficitTwice() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3));
+
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //not enough funds in the reward pool should trigger an insufficient link event
+    vm.expectEmit();
+
+    IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1);
+    contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+
+    //emit the event that is expected to be emitted
+    emit InsufficientLink(contractFees);
+
+    //process the fee
+    processFee(contractFees[0].poolId, payload, USER, address(native), 0);
+
+    //double check the rewardManager balance is 0
+    assertEq(getLinkBalance(address(rewardManager)), 0);
+
+    //simulate a deposit of link to cover the deficit
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    vm.expectEmit();
+    emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE);
+
+    //pay the deficit which will transfer link from the rewardManager to the rewardManager
+    payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN);
+
+    //check the rewardManager received the link
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //paying again should revert with 0
+    vm.expectRevert(ZERO_DEFICIT);
+
+    payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN);
+  }
+
+  function test_payLinkDeficitPaysAllFeesProcessed() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3));
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 2, USER);
+
+    //processing the fee will transfer the native from the user to the feeManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+
+    //check the deficit has been increased twice
+    assertEq(getLinkDeficit(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE * 2);
+
+    //double check the rewardManager balance is 0
+    assertEq(getLinkBalance(address(rewardManager)), 0);
+
+    //simulate a deposit of link to cover the deficit
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 2);
+
+    vm.expectEmit();
+    emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE * 2);
+
+    //pay the deficit which will transfer link from the rewardManager to the rewardManager
+    payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN);
+
+    //check the rewardManager received the link
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 2);
+  }
+
+  function test_payLinkDeficitOnlyCallableByAdmin() public {
+    vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR);
+
+    payLinkDeficit(DEFAULT_CONFIG_DIGEST, USER);
+  }
+
+  function test_revertOnSettingAnAddressZeroVerifier() public {
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.addVerifier(address(0));
+  }
+
+  function test_onlyCallableByOwnerReverts() public {
+    address STRANGER = address(999);
+    changePrank(STRANGER);
+    vm.expectRevert(bytes("Only callable by owner"));
+    feeManager.addVerifier(address(0));
+  }
+
+  function test_addVerifierExistingAddress() public {
+    address dummyAddress = address(998);
+    feeManager.addVerifier(dummyAddress);
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.addVerifier(dummyAddress);
+  }
+
+  function test_addVerifier() public {
+    address dummyAddress = address(998);
+    feeManager.addVerifier(dummyAddress);
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.addVerifier(dummyAddress);
+
+    // check calls to setFeeRecipients it should not error unauthorized
+    changePrank(dummyAddress);
+    bytes32 dummyConfigDigest = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](1);
+    recipients[0] = Common.AddressAndWeight(address(991), 1e18);
+    feeManager.setFeeRecipients(dummyConfigDigest, recipients);
+
+    // removing this verifier should result in unauthorized when calling  setFeeRecipients
+    changePrank(ADMIN);
+    feeManager.removeVerifier(dummyAddress);
+    changePrank(dummyAddress);
+    vm.expectRevert(UNAUTHORIZED_ERROR);
+    feeManager.setFeeRecipients(dummyConfigDigest, recipients);
+  }
+
+  function test_removeVerifierZeroAaddress() public {
+    address dummyAddress = address(0);
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.removeVerifier(dummyAddress);
+  }
+
+  function test_removeVerifierNonExistentAddress() public {
+    address dummyAddress = address(991);
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.removeVerifier(dummyAddress);
+  }
+
+  function test_setRewardManagerZeroAddress() public {
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    feeManager.setRewardManager(address(0));
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol
new file mode 100644
index 00000000000..30be694df2c
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Common} from "../../../libraries/Common.sol";
+import "./BaseDestinationFeeManager.t.sol";
+
+/**
+ * @title BaseFeeManagerTest
+ * @author Michael Fletcher
+ * @notice This contract will test the functionality of the feeManager's getFeeAndReward
+ */
+contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest {
+  function test_baseFeeIsAppliedForNative() public view {
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_baseFeeIsAppliedForLink() public view {
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_discountAIsNotAppliedWhenSetForOtherUsers() public {
+    //set the subscriber discount for another user
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), INVALID_ADDRESS);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_discountIsNotAppliedForInvalidTokenAddress() public {
+    //should revert with invalid address as it's not a configured token
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+
+    //set the subscriber discount for another user
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, INVALID_ADDRESS, FEE_SCALAR / 2, ADMIN);
+  }
+
+  function test_discountIsAppliedForLink() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be half the default
+    assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2);
+  }
+
+  function test_DiscountIsAppliedForNative() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be half the default
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE / 2);
+  }
+
+  function test_discountIsNoLongerAppliedAfterRemoving() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be half the default
+    assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2);
+
+    //remove the discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), 0, ADMIN);
+
+    //get the fee required by the feeManager
+    fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_surchargeIsApplied() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge
+    uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR);
+
+    //expected fee should the base fee offset by the surcharge and discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge);
+  }
+
+  function test_surchargeIsNotAppliedForLinkFee() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_surchargeIsNoLongerAppliedAfterRemoving() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge
+    uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR);
+
+    //expected fee should be the base fee offset by the surcharge and discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge);
+
+    //remove the surcharge
+    setNativeSurcharge(0, ADMIN);
+
+    //get the fee required by the feeManager
+    fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be the default
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_feeIsUpdatedAfterNewSurchargeIsApplied() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge
+    uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR);
+
+    //expected fee should the base fee offset by the surcharge and discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge);
+
+    //change the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge
+    expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR);
+
+    //expected fee should the base fee offset by the surcharge and discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge);
+  }
+
+  function test_surchargeIsAppliedForNativeFeeWithDiscount() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge quantity
+    uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR);
+
+    //calculate the expected discount quantity
+    uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge) / 2);
+
+    //expected fee should the base fee offset by the surcharge and discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge - expectedDiscount);
+  }
+
+  function test_emptyQuoteRevertsWithError() public {
+    //expect a revert
+    vm.expectRevert(INVALID_QUOTE_ERROR);
+
+    //get the fee required by the feeManager
+    getFee(getV3Report(DEFAULT_FEED_1_V3), address(0), USER);
+  }
+
+  function test_nativeSurcharge100Percent() public {
+    //set the surcharge
+    setNativeSurcharge(FEE_SCALAR, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be twice the base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2);
+  }
+
+  function test_nativeSurcharge0Percent() public {
+    //set the surcharge
+    setNativeSurcharge(0, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_nativeSurchargeCannotExceed100Percent() public {
+    //should revert if surcharge is greater than 100%
+    vm.expectRevert(INVALID_SURCHARGE_ERROR);
+
+    //set the surcharge above the max
+    setNativeSurcharge(FEE_SCALAR + 1, ADMIN);
+  }
+
+  function test_discountIsAppliedWith100PercentSurcharge() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //set the surcharge
+    setNativeSurcharge(FEE_SCALAR, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected discount quantity
+    uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE;
+
+    //fee should be twice the surcharge minus the discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2 - expectedDiscount);
+  }
+
+  function test_feeIsZeroWith100PercentDiscount() public {
+    //set the subscriber discount to 100%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be zero
+    assertEq(fee.amount, 0);
+  }
+
+  function test_feeIsUpdatedAfterDiscountIsRemoved() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected discount quantity
+    uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2;
+
+    //fee should be 50% of the base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount);
+
+    //remove the discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), 0, ADMIN);
+
+    //get the fee required by the feeManager
+    fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be the base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_feeIsUpdatedAfterNewDiscountIsApplied() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected discount quantity
+    uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2;
+
+    //fee should be 50% of the base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount);
+
+    //change the discount to 25%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 4, ADMIN);
+
+    //get the fee required by the feeManager
+    fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //expected discount is now 25%
+    expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 4;
+
+    //fee should be the base fee minus the expected discount
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount);
+  }
+
+  function test_setDiscountOver100Percent() public {
+    //should revert with invalid discount
+    vm.expectRevert(INVALID_DISCOUNT_ERROR);
+
+    //set the subscriber discount to over 100%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR + 1, ADMIN);
+  }
+
+  function test_surchargeIsNotAppliedWith100PercentDiscount() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 5;
+
+    //set the subscriber discount to 100%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN);
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be zero
+    assertEq(fee.amount, 0);
+  }
+
+  function test_nonAdminUserCanNotSetDiscount() public {
+    //should revert with unauthorized
+    vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR);
+
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, USER);
+  }
+
+  function test_surchargeFeeRoundsUpWhenUneven() public {
+    //native surcharge
+    uint256 nativeSurcharge = FEE_SCALAR / 3;
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected surcharge quantity
+    uint256 expectedSurcharge = (DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR;
+
+    //expected fee should the base fee offset by the expected surcharge
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge + 1);
+  }
+
+  function test_discountFeeRoundsDownWhenUneven() public {
+    //native surcharge
+    uint256 discount = FEE_SCALAR / 3;
+
+    //set the subscriber discount to 33.333%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected quantity
+    uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE * discount) / FEE_SCALAR);
+
+    //expected fee should the base fee offset by the expected surcharge
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount);
+  }
+
+  function test_reportWithNoExpiryOrFeeReturnsZero() public view {
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV1Report(DEFAULT_FEED_1_V1), getNativeQuote(), USER);
+
+    //fee should be zero
+    assertEq(fee.amount, 0);
+  }
+
+  function test_correctDiscountIsAppliedWhenBothTokensAreDiscounted() public {
+    //set the subscriber and native discounts
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 4, ADMIN);
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager for both tokens
+    Common.Asset memory linkFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+    Common.Asset memory nativeFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //calculate the expected discount quantity for each token
+    uint256 expectedDiscountLink = (DEFAULT_REPORT_LINK_FEE * FEE_SCALAR) / 4 / FEE_SCALAR;
+    uint256 expectedDiscountNative = (DEFAULT_REPORT_NATIVE_FEE * FEE_SCALAR) / 2 / FEE_SCALAR;
+
+    //check the fee calculation for each token
+    assertEq(linkFee.amount, DEFAULT_REPORT_LINK_FEE - expectedDiscountLink);
+    assertEq(nativeFee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscountNative);
+  }
+
+  function test_discountIsNotAppliedToOtherFeeds() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_2_V3), getNativeQuote(), USER);
+
+    //fee should be the base fee
+    assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_noFeeIsAppliedWhenReportHasZeroFee() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0),
+      getNativeQuote(),
+      USER
+    );
+
+    //fee should be zero
+    assertEq(fee.amount, 0);
+  }
+
+  function test_noFeeIsAppliedWhenReportHasZeroFeeAndDiscountAndSurchargeIsSet() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //set the surcharge
+    setNativeSurcharge(FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0),
+      getNativeQuote(),
+      USER
+    );
+
+    //fee should be zero
+    assertEq(fee.amount, 0);
+  }
+
+  function test_nativeSurchargeEventIsEmittedOnUpdate() public {
+    //native surcharge
+    uint64 nativeSurcharge = FEE_SCALAR / 3;
+
+    //an event should be emitted
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit NativeSurchargeUpdated(nativeSurcharge);
+
+    //set the surcharge
+    setNativeSurcharge(nativeSurcharge, ADMIN);
+  }
+
+  function test_getBaseRewardWithLinkQuote() public view {
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //the reward should equal the base fee
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_getRewardWithLinkQuoteAndLinkDiscount() public {
+    //set the link discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //the reward should equal the discounted base fee
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2);
+  }
+
+  function test_getRewardWithNativeQuote() public view {
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //the reward should equal the base fee in link
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_getRewardWithNativeQuoteAndSurcharge() public {
+    //set the native surcharge
+    setNativeSurcharge(FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //the reward should equal the base fee in link regardless of the surcharge
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_getRewardWithLinkDiscount() public {
+    //set the link discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //the reward should equal the discounted base fee
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2);
+  }
+
+  function test_getLinkFeeIsRoundedUp() public {
+    //set the link discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //the reward should equal .66% + 1 of the base fee due to a 33% discount rounded up
+    assertEq(fee.amount, (DEFAULT_REPORT_LINK_FEE * 2) / 3 + 1);
+  }
+
+  function test_getLinkRewardIsSameAsFee() public {
+    //set the link discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //check the reward is in link
+    assertEq(fee.assetAddress, address(link));
+
+    //the reward should equal .66% of the base fee due to a 33% discount rounded down
+    assertEq(reward.amount, fee.amount);
+  }
+
+  function test_getLinkRewardWithNativeQuoteAndSurchargeWithLinkDiscount() public {
+    //set the native surcharge
+    setNativeSurcharge(FEE_SCALAR / 2, ADMIN);
+
+    //set the link discount
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN);
+
+    //get the fee required by the feeManager
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //the reward should equal the base fee in link regardless of the surcharge
+    assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_testRevertIfReportHasExpired() public {
+    //expect a revert
+    vm.expectRevert(EXPIRED_REPORT_ERROR);
+
+    //get the fee required by the feeManager
+    getFee(
+      getV3ReportWithCustomExpiryAndFee(
+        DEFAULT_FEED_1_V3,
+        block.timestamp - 1,
+        DEFAULT_REPORT_LINK_FEE,
+        DEFAULT_REPORT_NATIVE_FEE
+      ),
+      getNativeQuote(),
+      USER
+    );
+  }
+
+  function test_discountIsReturnedForLink() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN);
+
+    //get the fee applied
+    uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER);
+
+    //fee should be half the default
+    assertEq(discount, FEE_SCALAR / 2);
+  }
+
+  function test_DiscountIsReturnedForNative() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //get the discount applied
+    uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be half the default
+    assertEq(discount, FEE_SCALAR / 2);
+  }
+
+  function test_DiscountIsReturnedForNativeWithSurcharge() public {
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //set the surcharge
+    setNativeSurcharge(FEE_SCALAR / 5, ADMIN);
+
+    //get the discount applied
+    uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    //fee should be half the default
+    assertEq(discount, FEE_SCALAR / 2);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol
new file mode 100644
index 00000000000..0880352dca4
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol
@@ -0,0 +1,492 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Common} from "../../../libraries/Common.sol";
+import "./BaseDestinationFeeManager.t.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+/**
+ * @title BaseFeeManagerTest
+ * @author Michael Fletcher
+ * @notice This contract will test the functionality of the feeManager processFee
+ */
+contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest {
+  function setUp() public override {
+    super.setUp();
+  }
+
+  function test_nonAdminProxyUserCannotProcessFee() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //should revert as the user is not the owner
+    vm.expectRevert(UNAUTHORIZED_ERROR);
+
+    //process the fee
+    ProcessFeeAsUser(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0, USER);
+  }
+
+  function test_processFeeAsProxy() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //approve the link to be transferred from the from the subscriber to the rewardManager
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+
+    //check the link has been transferred
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the user has had the link fee deducted
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_processFeeIfSubscriberIsSelf() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //expect a revert due to the feeManager being the subscriber
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+
+    //process the fee will fail due to assertion
+    processFee(DEFAULT_CONFIG_DIGEST, payload, address(feeManager), address(native), 0);
+  }
+
+  function test_processFeeWithWithEmptyQuotePayload() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //expect a revert as the quote is invalid
+    vm.expectRevert();
+
+    //processing the fee will transfer the link by default
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0);
+  }
+
+  function test_processFeeWithWithZeroQuotePayload() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //expect a revert as the quote is invalid
+    vm.expectRevert(INVALID_QUOTE_ERROR);
+
+    //processing the fee will transfer the link by default
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, INVALID_ADDRESS, 0);
+  }
+
+  function test_processFeeWithWithCorruptQuotePayload() public {
+    //get the default payload
+    bytes memory payload = abi.encode(
+      [DEFAULT_CONFIG_DIGEST, 0, 0],
+      getV3Report(DEFAULT_FEED_1_V3),
+      new bytes32[](1),
+      new bytes32[](1),
+      bytes32("")
+    );
+
+    //expect an evm revert as the quote is corrupt
+    vm.expectRevert();
+
+    //processing the fee will not withdraw anything as there is no fee to collect
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+  }
+
+  function test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1));
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0);
+  }
+
+  function test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1));
+
+    //processing the fee will not withdraw anything as there is no fee to collect
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+  }
+
+  function test_processFeeNative() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //processing the fee will transfer the native from the user to the feeManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+
+    //check the native has been transferred
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check the link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the feeManager has had the link deducted, the remaining balance should be 0
+    assertEq(getLinkBalance(address(feeManager)), 0);
+
+    //check the subscriber has had the native deducted
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_processFeeEmitsEventIfNotEnoughLink() public {
+    //simulate a deposit of half the link required for the fee
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE / 2);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //expect an emit as there's not enough link
+    vm.expectEmit();
+
+    IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1);
+    contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+
+    //emit the event that is expected to be emitted
+    emit InsufficientLink(contractFees);
+
+    //processing the fee will transfer the native from the user to the feeManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+
+    //check the native has been transferred
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check no link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), 0);
+    assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE / 2);
+
+    //check the subscriber has had the native deducted
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_processFeeWithUnwrappedNative() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //only the proxy or admin can call processFee, they will pass in the native value on the users behalf
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check the native has been transferred and converted to wrapped native
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE);
+    assertEq(getNativeUnwrappedBalance(address(feeManager)), 0);
+
+    //check the link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the feeManager has had the link deducted, the remaining balance should be 0
+    assertEq(getLinkBalance(address(feeManager)), 0);
+
+    //check the subscriber has had the native deducted
+    assertEq(getNativeUnwrappedBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_processFeeWithUnwrappedNativeShortFunds() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //expect a revert as not enough funds
+    vm.expectRevert(INVALID_DEPOSIT_ERROR);
+
+    //only the proxy or admin can call processFee, they will pass in the native value on the users behalf
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE - 1);
+  }
+
+  function test_processFeeWithUnwrappedNativeLinkAddress() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //expect a revert as not enough funds
+    vm.expectRevert(INSUFFICIENT_ALLOWANCE_ERROR);
+
+    //the change will be returned and the user will attempted to be billed in LINK
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE - 1);
+  }
+
+  function test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() public {
+    //approve the link to be transferred from the from the subscriber to the rewardManager
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, PROXY);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy.
+    processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(link), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check the native unwrapped is no longer in the account
+    assertEq(getNativeBalance(address(feeManager)), 0);
+    assertEq(getNativeUnwrappedBalance(address(feeManager)), 0);
+
+    //check the link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the feeManager has had the link deducted, the remaining balance should be 0
+    assertEq(getLinkBalance(address(feeManager)), 0);
+
+    //native should not be deducted
+    assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_processFeeWithUnwrappedNativeWithExcessiveFee() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy.
+    processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(native), DEFAULT_REPORT_NATIVE_FEE * 2);
+
+    //check the native has been transferred and converted to wrapped native
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE);
+    assertEq(getNativeUnwrappedBalance(address(feeManager)), 0);
+
+    //check the link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the feeManager has had the link deducted, the remaining balance should be 0
+    assertEq(getLinkBalance(address(feeManager)), 0);
+
+    //check the subscriber has had the native deducted
+    assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_processFeeUsesCorrectDigest() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //approve the link to be transferred from the from the subscriber to the rewardManager
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+
+    //check the link has been transferred
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the user has had the link fee deducted
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+
+    //check funds have been paid to the reward manager
+    assertEq(rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_V1PayloadVerifies() public {
+    //replicate a default payload
+    bytes memory payload = abi.encode(
+      [DEFAULT_CONFIG_DIGEST, 0, 0],
+      getV2Report(DEFAULT_FEED_1_V1),
+      new bytes32[](1),
+      new bytes32[](1),
+      bytes32("")
+    );
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0);
+  }
+
+  function test_V2PayloadVerifies() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2));
+
+    //approve the link to be transferred from the from the subscriber to the rewardManager
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+
+    //check the link has been transferred
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the user has had the link fee deducted
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_V2PayloadWithoutQuoteFails() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2));
+
+    //expect a revert as the quote is invalid
+    vm.expectRevert();
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0);
+  }
+
+  function test_V2PayloadWithoutZeroFee() public {
+    //get the default payload
+    bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2));
+
+    //expect a revert as the quote is invalid
+    vm.expectRevert();
+
+    //processing the fee will transfer the link from the user to the rewardManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+  }
+
+  function test_processFeeWithInvalidReportVersionFailsToDecode() public {
+    bytes memory data = abi.encode(0x0000100000000000000000000000000000000000000000000000000000000000);
+
+    //get the default payload
+    bytes memory payload = getPayload(data);
+
+    //serialization will fail as there is no report to decode
+    vm.expectRevert();
+
+    //processing the fee will not withdraw anything as there is no fee to collect
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+  }
+
+  function test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() public {
+    //get the default payload
+    bytes memory payload = getPayload(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0)
+    );
+
+    //call processFee should not revert as the fee is 0
+    processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(native), 0);
+  }
+
+  function test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() public {
+    //get the default payload
+    bytes memory payload = getPayload(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0)
+    );
+
+    //approve the link to be transferred from the from the subscriber to the rewardManager
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+
+    //processing the fee will transfer the link to the rewardManager from the user
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+
+    //check the link has been transferred
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the user has had the link fee deducted
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //get the default payload
+    bytes memory payload = getPayload(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)
+    );
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //processing the fee will transfer the native from the user to the feeManager
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+
+    //check the native has been transferred
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check no link has been transferred to the rewardManager
+    assertEq(getLinkBalance(address(rewardManager)), 0);
+
+    //check the feeManager has had no link deducted
+    assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE);
+
+    //check the subscriber has had the native deducted
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+  }
+
+  function test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() public {
+    //get the default payload
+    bytes memory payload = getPayload(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)
+    );
+
+    //call processFee should not revert as the fee is 0
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0);
+  }
+
+  function test_processFeeWithZeroNativeNonZeroLinkReturnsChange() public {
+    //get the default payload
+    bytes memory payload = getPayload(
+      getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)
+    );
+
+    //call processFee should not revert as the fee is 0
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE);
+
+    //check the change has been returned
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_V1PayloadVerifiesAndReturnsChange() public {
+    //emulate a V1 payload with no quote
+    bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1));
+
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), DEFAULT_REPORT_NATIVE_FEE);
+
+    //Fee manager should not contain any native
+    assertEq(address(feeManager).balance, 0);
+    assertEq(getNativeBalance(address(feeManager)), 0);
+
+    //check the unused native passed in is returned
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_processFeeWithDiscountEmitsEvent() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //set the subscriber discount to 50%
+    setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN);
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE / 2, USER);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+    Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+    uint256 appliedDiscount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER);
+
+    vm.expectEmit();
+
+    emit DiscountApplied(DEFAULT_CONFIG_DIGEST, USER, fee, reward, appliedDiscount);
+
+    //call processFee should not revert as the fee is 0
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+  }
+
+  function test_processFeeWithNoDiscountDoesNotEmitEvent() public {
+    //simulate a deposit of link for the conversion pool
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+
+    //approve the native to be transferred from the user
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    //get the default payload
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    //call processFee should not revert as the fee is 0
+    processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0);
+
+    //no logs should have been emitted
+    assertEq(vm.getRecordedLogs().length, 0);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol
new file mode 100644
index 00000000000..a50441bed67
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import "./BaseDestinationFeeManager.t.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+/**
+ * @title BaseFeeManagerTest
+ * @author Michael Fletcher
+ * @notice This contract will test the functionality of the feeManager processFee
+ */
+contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest {
+  uint256 internal constant NUMBER_OF_REPORTS = 5;
+
+  function setUp() public override {
+    super.setUp();
+  }
+
+  function test_processMultipleLinkReports() public {
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS, USER);
+
+    processFee(poolIds, payloads, USER, address(link), DEFAULT_NATIVE_MINT_QUANTITY);
+
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(feeManager)), 0);
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+
+    //the subscriber (user) should receive funds back and not the proxy, although when live the proxy will forward the funds sent and not cover it seen here
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+    assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_processMultipleWrappedNativeReports() public {
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1);
+
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS, USER);
+
+    processFee(poolIds, payloads, USER, address(native), 0);
+
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(feeManager)), 1);
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS);
+  }
+
+  function test_processMultipleUnwrappedNativeReports() public {
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1);
+
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2);
+
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(feeManager)), 1);
+
+    assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS);
+  }
+
+  function test_processV1V2V3Reports() public {
+    mintLink(address(feeManager), 1);
+
+    bytes memory payloadV1 = abi.encode(
+      [DEFAULT_CONFIG_DIGEST, 0, 0],
+      getV1Report(DEFAULT_FEED_1_V1),
+      new bytes32[](1),
+      new bytes32[](1),
+      bytes32("")
+    );
+
+    bytes memory linkPayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2));
+    bytes memory linkPayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](5);
+    payloads[0] = payloadV1;
+    payloads[1] = linkPayloadV2;
+    payloads[2] = linkPayloadV2;
+    payloads[3] = linkPayloadV3;
+    payloads[4] = linkPayloadV3;
+
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * 4, USER);
+
+    bytes32[] memory poolIds = new bytes32[](5);
+    for (uint256 i = 0; i < 5; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    processFee(poolIds, payloads, USER, address(link), 0);
+
+    assertEq(getNativeBalance(address(feeManager)), 0);
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4);
+    assertEq(getLinkBalance(address(feeManager)), 1);
+
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * 4);
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - 0);
+  }
+
+  function test_processV1V2V3ReportsWithUnwrapped() public {
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 4 + 1);
+
+    bytes memory payloadV1 = abi.encode(
+      [DEFAULT_CONFIG_DIGEST, 0, 0],
+      getV1Report(DEFAULT_FEED_1_V1),
+      new bytes32[](1),
+      new bytes32[](1),
+      bytes32("")
+    );
+
+    bytes memory nativePayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2));
+    bytes memory nativePayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](5);
+    payloads[0] = payloadV1;
+    payloads[1] = nativePayloadV2;
+    payloads[2] = nativePayloadV2;
+    payloads[3] = nativePayloadV3;
+    payloads[4] = nativePayloadV3;
+
+    bytes32[] memory poolIds = new bytes32[](5);
+    for (uint256 i = 0; i < 5; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 4);
+
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 4);
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4);
+    assertEq(getLinkBalance(address(feeManager)), 1);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 4);
+    assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_processMultipleV1Reports() public {
+    bytes memory payload = abi.encode(
+      [DEFAULT_CONFIG_DIGEST, 0, 0],
+      getV1Report(DEFAULT_FEED_1_V1),
+      new bytes32[](1),
+      new bytes32[](1),
+      bytes32("")
+    );
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 5);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+    assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+  }
+
+  function test_eventIsEmittedIfNotEnoughLink() public {
+    bytes memory nativePayload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](5);
+    payloads[0] = nativePayload;
+    payloads[1] = nativePayload;
+    payloads[2] = nativePayload;
+    payloads[3] = nativePayload;
+    payloads[4] = nativePayload;
+
+    approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 5, USER);
+
+    IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](5);
+    payments[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+    payments[1] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+    payments[2] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+    payments[3] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+    payments[4] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE));
+
+    vm.expectEmit();
+
+    bytes32[] memory poolIds = new bytes32[](5);
+    for (uint256 i = 0; i < 5; ++i) {
+      poolIds[i] = payments[i].poolId;
+    }
+
+    emit InsufficientLink(payments);
+
+    processFee(poolIds, payloads, USER, address(native), 0);
+
+    assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 5);
+    assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5);
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY);
+  }
+
+  function test_processPoolIdsPassedMismatched() public {
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1);
+
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    // poolIds passed are different that number of reports in payload
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS - 1);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS - 1; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    vm.expectRevert(POOLID_MISMATCH_ERROR);
+    processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2);
+  }
+
+  function test_poolIdsCannotBeZeroAddress() public {
+    mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1);
+
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+
+    poolIds[2] = 0x000;
+    vm.expectRevert(INVALID_ADDRESS_ERROR);
+    processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2);
+  }
+
+  function test_rewardsAreCorrectlySentToEachAssociatedPoolWhenVerifyingInBulk() public {
+    bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3));
+
+    bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      payloads[i] = payload;
+    }
+
+    bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS - 1; ++i) {
+      poolIds[i] = DEFAULT_CONFIG_DIGEST;
+    }
+    poolIds[NUMBER_OF_REPORTS - 1] = DEFAULT_CONFIG_DIGEST2;
+
+    approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS, USER);
+
+    // Checking no rewards yet for each pool
+    for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) {
+      bytes32 p_id = poolIds[i];
+      uint256 poolDeficit = rewardManager.s_totalRewardRecipientFees(p_id);
+      assertEq(poolDeficit, 0);
+    }
+
+    processFee(poolIds, payloads, USER, address(link), DEFAULT_NATIVE_MINT_QUANTITY);
+
+    assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+    assertEq(getLinkBalance(address(feeManager)), 0);
+    assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+    assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY);
+
+    // Checking each pool got the correct rewards
+    uint256 expectedRewards = DEFAULT_REPORT_LINK_FEE * (NUMBER_OF_REPORTS - 1);
+    uint256 poolRewards = rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST);
+    assertEq(poolRewards, expectedRewards);
+
+    expectedRewards = DEFAULT_REPORT_LINK_FEE;
+    poolRewards = rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST2);
+    assertEq(poolRewards, expectedRewards);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol
new file mode 100644
index 00000000000..46ec7fff3b5
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.19;
+
+import {IDestinationFeeManager} from "../../interfaces/IDestinationFeeManager.sol";
+
+contract DestinationFeeManagerProxy {
+  IDestinationFeeManager internal s_feeManager;
+
+  function processFee(bytes32 poolId, bytes calldata payload, bytes calldata parameterPayload) public payable {
+    s_feeManager.processFee{value: msg.value}(poolId, payload, parameterPayload, msg.sender);
+  }
+
+  function processFeeBulk(
+    bytes32[] memory poolIds,
+    bytes[] calldata payloads,
+    bytes calldata parameterPayload
+  ) public payable {
+    s_feeManager.processFeeBulk{value: msg.value}(poolIds, payloads, parameterPayload, msg.sender);
+  }
+
+  function setDestinationFeeManager(IDestinationFeeManager feeManager) public {
+    s_feeManager = feeManager;
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol
new file mode 100644
index 00000000000..7cafb1629db
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Test} from "forge-std/Test.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+/**
+ * @title DestinationRewardManagerTest
+ * @author Michael Fletcher
+ * @notice Base class for all reward manager tests
+ * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup a primary and secondary pool
+ */
+contract BaseDestinationRewardManagerTest is Test {
+  //contracts
+  ERC20Mock internal asset;
+  ERC20Mock internal unsupported;
+  DestinationRewardManager internal rewardManager;
+
+  //default address for unregistered recipient
+  address internal constant INVALID_ADDRESS = address(0);
+  //contract owner
+  address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN"))));
+  //address to represent feeManager contract
+  address internal constant FEE_MANAGER = address(uint160(uint256(keccak256("FEE_MANAGER"))));
+  //address to represent another feeManager
+  address internal constant FEE_MANAGER_2 = address(uint160(uint256(keccak256("FEE_MANAGER_2"))));
+  //a general user
+  address internal constant USER = address(uint160(uint256(keccak256("USER"))));
+
+  //default recipients configured in reward manager
+  address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1"))));
+  address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2"))));
+  address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3"))));
+  address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4"))));
+  address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5"))));
+  address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6"))));
+  address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7"))));
+
+  //additional recipients not in the reward manager
+  address internal constant DEFAULT_RECIPIENT_8 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_8"))));
+  address internal constant DEFAULT_RECIPIENT_9 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_9"))));
+
+  //two pools should be enough to test all edge cases
+  bytes32 internal constant PRIMARY_POOL_ID = keccak256("primary_pool");
+  bytes32 internal constant SECONDARY_POOL_ID = keccak256("secondary_pool");
+  bytes32 internal constant INVALID_POOL_ID = keccak256("invalid_pool");
+  bytes32 internal constant ZERO_POOL_ID = bytes32(0);
+
+  //convenience arrays of all pool combinations used for testing
+  bytes32[] internal PRIMARY_POOL_ARRAY = [PRIMARY_POOL_ID];
+  bytes32[] internal SECONDARY_POOL_ARRAY = [SECONDARY_POOL_ID];
+  bytes32[] internal ALL_POOLS = [PRIMARY_POOL_ID, SECONDARY_POOL_ID];
+
+  //erc20 config
+  uint256 internal constant DEFAULT_MINT_QUANTITY = 100 ether;
+
+  //reward scalar (this should match the const in the contract)
+  uint64 internal constant POOL_SCALAR = 1e18;
+  uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100;
+  uint64 internal constant FIFTY_PERCENT = POOL_SCALAR / 2;
+  uint64 internal constant TEN_PERCENT = POOL_SCALAR / 10;
+
+  //the selector for each error
+  bytes4 internal immutable UNAUTHORIZED_ERROR_SELECTOR = DestinationRewardManager.Unauthorized.selector;
+  bytes4 internal immutable INVALID_ADDRESS_ERROR_SELECTOR = DestinationRewardManager.InvalidAddress.selector;
+  bytes4 internal immutable INVALID_WEIGHT_ERROR_SELECTOR = DestinationRewardManager.InvalidWeights.selector;
+  bytes4 internal immutable INVALID_POOL_ID_ERROR_SELECTOR = DestinationRewardManager.InvalidPoolId.selector;
+  bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner";
+  bytes4 internal immutable INVALID_POOL_LENGTH_SELECTOR = DestinationRewardManager.InvalidPoolLength.selector;
+
+  // Events emitted within the reward manager
+  event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients);
+  event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity);
+  event FeeManagerUpdated(address newProxyAddress);
+  event FeePaid(IDestinationRewardManager.FeePayment[] payments, address payee);
+
+  function setUp() public virtual {
+    //change to admin user
+    vm.startPrank(ADMIN);
+
+    //init required contracts
+    _initializeERC20Contracts();
+    _initializeRewardManager();
+  }
+
+  function _initializeERC20Contracts() internal {
+    //create the contracts
+    asset = new ERC20Mock("ASSET", "AST", ADMIN, 0);
+    unsupported = new ERC20Mock("UNSUPPORTED", "UNS", ADMIN, 0);
+
+    //mint some tokens to the admin
+    asset.mint(ADMIN, DEFAULT_MINT_QUANTITY);
+    unsupported.mint(ADMIN, DEFAULT_MINT_QUANTITY);
+
+    //mint some tokens to the user
+    asset.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY);
+    unsupported.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY);
+  }
+
+  function _initializeRewardManager() internal {
+    //create the contract
+    rewardManager = new DestinationRewardManager(address(asset));
+
+    rewardManager.addFeeManager(FEE_MANAGER);
+  }
+
+  function createPrimaryPool() public {
+    rewardManager.setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients());
+  }
+
+  function createSecondaryPool() public {
+    rewardManager.setRewardRecipients(SECONDARY_POOL_ID, getSecondaryRecipients());
+  }
+
+  //override this to test variations of different recipients. changing this function will require existing tests to be updated as constants are hardcoded to be explicit
+  function getPrimaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+
+    //init each recipient with even weights. 2500 = 25% of pool
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4);
+
+    return recipients;
+  }
+
+  function getPrimaryRecipientAddresses() public pure returns (address[] memory) {
+    //array of recipients
+    address[] memory recipients = new address[](4);
+
+    recipients[0] = DEFAULT_RECIPIENT_1;
+    recipients[1] = DEFAULT_RECIPIENT_2;
+    recipients[2] = DEFAULT_RECIPIENT_3;
+    recipients[3] = DEFAULT_RECIPIENT_4;
+
+    return recipients;
+  }
+
+  //override this to test variations of different recipients.
+  function getSecondaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+
+    //init each recipient with even weights. 2500 = 25% of pool
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4);
+
+    return recipients;
+  }
+
+  function getSecondaryRecipientAddresses() public pure returns (address[] memory) {
+    //array of recipients
+    address[] memory recipients = new address[](4);
+
+    recipients[0] = DEFAULT_RECIPIENT_1;
+    recipients[1] = DEFAULT_RECIPIENT_5;
+    recipients[2] = DEFAULT_RECIPIENT_6;
+    recipients[3] = DEFAULT_RECIPIENT_7;
+
+    return recipients;
+  }
+
+  function addFundsToPool(bytes32 poolId, Common.Asset memory amount, address sender) public {
+    IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1);
+    payments[0] = IDestinationRewardManager.FeePayment(poolId, uint192(amount.amount));
+
+    addFundsToPool(payments, sender);
+  }
+
+  function addFundsToPool(IDestinationRewardManager.FeePayment[] memory payments, address sender) public {
+    //record the current address and switch to the sender
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    uint256 totalPayment;
+    for (uint256 i; i < payments.length; ++i) {
+      totalPayment += payments[i].amount;
+    }
+
+    //approve the amount being paid into the pool
+    ERC20Mock(address(asset)).approve(address(rewardManager), totalPayment);
+
+    //this represents the verifier adding some funds to the pool
+    rewardManager.onFeePaid(payments, sender);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function getAsset(uint256 quantity) public view returns (Common.Asset memory) {
+    return Common.Asset(address(asset), quantity);
+  }
+
+  function getAssetBalance(address addr) public view returns (uint256) {
+    return asset.balanceOf(addr);
+  }
+
+  function claimRewards(bytes32[] memory poolIds, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //claim the rewards
+    rewardManager.claimRewards(poolIds);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //pay the recipients
+    rewardManager.payRecipients(poolId, recipients);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //pay the recipients
+    rewardManager.setRewardRecipients(poolId, recipients);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function setFeeManager(address feeManager, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //update the proxy
+    rewardManager.addFeeManager(feeManager);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //pay the recipients
+    rewardManager.updateRewardRecipients(poolId, recipients);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol
new file mode 100644
index 00000000000..c0a67d08758
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol
@@ -0,0 +1,790 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+/**
+ * @title DestinationRewardManagerClaimTest
+ * @author Michael Fletcher
+ * @notice This contract will test the claim functionality of the RewardManager contract.
+ */
+contract DestinationRewardManagerClaimTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_claimAllRecipients() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+  }
+
+  function test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() public {
+    //add funds to a different pool to ensure they're not claimed
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //create an array containing duplicate poolIds
+    bytes32[] memory poolIds = new bytes32[](2);
+    poolIds[0] = PRIMARY_POOL_ID;
+    poolIds[1] = PRIMARY_POOL_ID;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(poolIds, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //the pool should still have the remaining
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_claimSingleRecipient() public {
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0];
+
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount);
+  }
+
+  function test_claimMultipleRecipients() public {
+    //claim the individual rewards for each recipient
+    claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr);
+    claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount);
+    assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - (expectedRecipientAmount * 2));
+  }
+
+  function test_claimUnregisteredRecipient() public {
+    //claim the rewards for a recipient who isn't in this pool
+    claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr);
+
+    //check the recipients didn't receive any fees from this pool
+    assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_claimUnevenAmountRoundsDown() public {
+    //adding 1 to the pool should leave 1 wei worth of dust, which the contract doesn't handle due to it being economically infeasible
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(1), FEE_MANAGER);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //check the rewardManager has the remaining quantity equals 1 wei
+    assertEq(getAssetBalance(address(rewardManager)), 1);
+  }
+
+  function test_claimUnregisteredPoolId() public {
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0];
+
+    //claim the individual rewards for this recipient
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //check the recipients balance is still 0 as there's no pool to receive fees from
+    assertEq(getAssetBalance(recipient.addr), 0);
+
+    //check the rewardManager has the full amount
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_singleRecipientClaimMultipleDeposits() public {
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0];
+
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount);
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+    //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit
+    assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2);
+
+    //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2));
+  }
+
+  function test_recipientsClaimMultipleDeposits() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //the reward manager balance should be 0 as all of the funds have been claimed
+    assertEq(getAssetBalance(address(rewardManager)), 0);
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //expected recipient amount is 1/4 of the pool deposit
+      expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2;
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //the reward manager balance should again be 0 as all of the funds have been claimed
+    assertEq(getAssetBalance(address(rewardManager)), 0);
+  }
+
+  function test_eventIsEmittedUponClaim() public {
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0];
+
+    //expect an emit
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit RewardsClaimed(PRIMARY_POOL_ID, recipient.addr, uint192(POOL_DEPOSIT_AMOUNT / 4));
+
+    //claim the individual rewards for each recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+  }
+
+  function test_eventIsNotEmittedUponUnsuccessfulClaim() public {
+    //record logs to check no events were emitted
+    vm.recordLogs();
+
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0];
+
+    //claim the individual rewards for each recipient
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //no logs should have been emitted
+    assertEq(vm.getRecordedLogs().length, 0);
+  }
+}
+
+contract DestinationRewardManagerRecipientClaimMultiplePoolsTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a two pools
+    createPrimaryPool();
+    createSecondaryPool();
+
+    //add funds to each of the pools to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_claimAllRecipientsSinglePool() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //check the pool balance is still equal to DEPOSIT_AMOUNT as the test only claims for one of the pools
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_claimMultipleRecipientsSinglePool() public {
+    //claim the individual rewards for each recipient
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount);
+    assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2));
+  }
+
+  function test_claimMultipleRecipientsMultiplePools() public {
+    //claim the individual rewards for each recipient
+    claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr);
+    claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr);
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr);
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received. The first recipient is shared across both pools so should receive 1/4 of each pool
+    assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount * 2);
+    assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount);
+    assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_claimAllRecipientsMultiplePools() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i = 1; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //claim funds for each recipient within the pool
+    for (uint256 i = 1; i < getSecondaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory secondaryRecipient = getSecondaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(SECONDARY_POOL_ARRAY, secondaryRecipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(secondaryRecipient.addr), expectedRecipientAmount);
+    }
+
+    //special case to handle the first recipient of each pool as they're the same address
+    Common.AddressAndWeight memory commonRecipient = getPrimaryRecipients()[0];
+
+    //claim the individual rewards for each pool
+    claimRewards(PRIMARY_POOL_ARRAY, commonRecipient.addr);
+    claimRewards(SECONDARY_POOL_ARRAY, commonRecipient.addr);
+
+    //check the balance matches the ratio the recipient should have received, which is 1/4 of each deposit for each pool
+    assertEq(getAssetBalance(commonRecipient.addr), expectedRecipientAmount * 2);
+  }
+
+  function test_claimSingleUniqueRecipient() public {
+    //the first recipient of the secondary pool is in both pools, so take the second recipient which is unique
+    Common.AddressAndWeight memory recipient = getSecondaryRecipients()[1];
+
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //the recipient should have received 1/4 of the deposit amount
+    uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //the recipient should have received 1/4 of the deposit amount
+    assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount);
+  }
+
+  function test_claimSingleRecipientMultiplePools() public {
+    //the first recipient of the secondary pool is in both pools
+    Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0];
+
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //the recipient should have received 1/4 of the deposit amount for each pool
+    uint256 recipientExpectedAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2;
+
+    //this recipient belongs in both pools so should have received 1/4 of each
+    assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount);
+  }
+
+  function test_claimUnregisteredRecipient() public {
+    //claim the individual rewards for this recipient
+    claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr);
+    claimRewards(SECONDARY_POOL_ARRAY, getPrimaryRecipients()[1].addr);
+
+    //check the recipients didn't receive any fees from this pool
+    assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0);
+    assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), 0);
+
+    //check the rewardManager has the remaining quantity
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2);
+  }
+
+  function test_claimUnevenAmountRoundsDown() public {
+    //adding an uneven amount of dust to each pool, this should round down to the nearest whole number with 4 remaining in the contract
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(3), FEE_MANAGER);
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(1), FEE_MANAGER);
+
+    //the recipient should have received 1/4 of the deposit amount for each pool
+    uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount);
+    }
+
+    //special case to handle the first recipient of each pool as they're the same address
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+
+    //check the balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), recipientExpectedAmount * 2);
+
+    //claim funds for each recipient of the secondary pool except the first
+    for (uint256 i = 1; i < getSecondaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount);
+    }
+
+    //contract should have 4 remaining
+    assertEq(getAssetBalance(address(rewardManager)), 4);
+  }
+
+  function test_singleRecipientClaimMultipleDeposits() public {
+    //get the recipient that is claiming
+    Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0];
+
+    //claim the individual rewards for this recipient
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //the recipient should have received 1/4 of the deposit amount
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit plus the deposit from the second pool
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - expectedRecipientAmount);
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //claim the individual rewards for this recipient
+    claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+    //the recipient should have received 1/4 of the next deposit amount
+    expectedRecipientAmount += POOL_DEPOSIT_AMOUNT / 4;
+
+    //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit
+    assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+
+    //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 3 - expectedRecipientAmount);
+  }
+
+  function test_recipientsClaimMultipleDeposits() public {
+    //the recipient should have received 1/4 of the deposit amount
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getSecondaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //the reward manager balance should contain only the funds of the secondary pool
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //special case to handle the first recipient of each pool as they're the same address
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+
+    //check the balance matches the ratio the recipient should have received
+    assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount * 2);
+
+    //claim funds for each recipient within the pool except the first
+    for (uint256 i = 1; i < getSecondaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(SECONDARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2);
+    }
+
+    //the reward manager balance should again be the balance of the secondary pool as the primary pool has been emptied twice
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_claimEmptyPoolWhenSecondPoolContainsFunds() public {
+    //the recipient should have received 1/4 of the deposit amount
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //claim all rewards for each recipient in the primary pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //claim all the rewards again for the first recipient as that address is a member of both pools
+    claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+
+    //check the balance
+    assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount);
+  }
+
+  function test_getRewardsAvailableToRecipientInBothPools() public {
+    //get index 0 as this recipient is in both default pools
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(
+      getPrimaryRecipients()[0].addr,
+      0,
+      type(uint256).max
+    );
+
+    //check the recipient is in both pools
+    assertEq(poolIds[0], PRIMARY_POOL_ID);
+    assertEq(poolIds[1], SECONDARY_POOL_ID);
+  }
+
+  function test_getRewardsAvailableToRecipientInSinglePool() public {
+    //get index 0 as this recipient is in both default pools
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(
+      getPrimaryRecipients()[1].addr,
+      0,
+      type(uint256).max
+    );
+
+    //check the recipient is in both pools
+    assertEq(poolIds[0], PRIMARY_POOL_ID);
+    assertEq(poolIds[1], ZERO_POOL_ID);
+  }
+
+  function test_getRewardsAvailableToRecipientInNoPools() public view {
+    //get index 0 as this recipient is in both default pools
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, 0, type(uint256).max);
+
+    //check the recipient is in neither pool
+    assertEq(poolIds[0], ZERO_POOL_ID);
+    assertEq(poolIds[1], ZERO_POOL_ID);
+  }
+
+  function test_getRewardsAvailableToRecipientInBothPoolsWhereAlreadyClaimed() public {
+    //get index 0 as this recipient is in both default pools
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(
+      getPrimaryRecipients()[0].addr,
+      0,
+      type(uint256).max
+    );
+
+    //check the recipient is in both pools
+    assertEq(poolIds[0], PRIMARY_POOL_ID);
+    assertEq(poolIds[1], SECONDARY_POOL_ID);
+
+    //claim the rewards for each pool
+    claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr);
+    claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr);
+
+    //get the available pools again
+    poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, type(uint256).max);
+
+    //user should not be in any pool
+    assertEq(poolIds[0], ZERO_POOL_ID);
+    assertEq(poolIds[1], ZERO_POOL_ID);
+  }
+
+  function test_getAvailableRewardsCursorCannotBeGreaterThanTotalPools() public {
+    vm.expectRevert(INVALID_POOL_LENGTH_SELECTOR);
+
+    rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, type(uint256).max, 0);
+  }
+
+  function test_getAvailableRewardsCursorAndTotalPoolsEqual() public {
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 2, 2);
+
+    assertEq(poolIds.length, 0);
+  }
+
+  function test_getAvailableRewardsCursorSingleResult() public {
+    bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, 1);
+
+    assertEq(poolIds[0], PRIMARY_POOL_ID);
+  }
+}
+
+contract DestinationRewardManagerRecipientClaimDifferentWeightsTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+
+    //init each recipient with uneven weights
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 8);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 6);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 4);
+
+    return recipients;
+  }
+
+  function test_allRecipientsClaimingReceiveExpectedAmount() public {
+    //loop all the recipients and claim their expected amount
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //the recipient should have received a share proportional to their weight
+      uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR;
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+  }
+}
+
+contract DestinationRewardManagerRecipientClaimUnevenWeightTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+  }
+
+  function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2);
+
+    uint64 oneThird = POOL_SCALAR / 3;
+
+    //init each recipient with even weights.
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, oneThird);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 2 * oneThird + 1);
+
+    return recipients;
+  }
+
+  function test_allRecipientsClaimingReceiveExpectedAmountWithSmallDeposit() public {
+    //add a smaller amount of funds to the pool
+    uint256 smallDeposit = 1e8;
+
+    //add a smaller amount of funds to the pool
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(smallDeposit), FEE_MANAGER);
+
+    //loop all the recipients and claim their expected amount
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //the recipient should have received a share proportional to their weight
+      uint256 expectedRecipientAmount = (smallDeposit * recipient.weight) / POOL_SCALAR;
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //smaller deposits will consequently have less precision and will not be able to be split as evenly, the remaining 1 will be lost due to 333...|... being paid out instead of 333...4|
+    assertEq(getAssetBalance(address(rewardManager)), 1);
+  }
+
+  function test_allRecipientsClaimingReceiveExpectedAmount() public {
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //loop all the recipients and claim their expected amount
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //claim the individual rewards for each recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //the recipient should have received a share proportional to their weight
+      uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR;
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+
+    //their should be 0 wei left over indicating a successful split
+    assertEq(getAssetBalance(address(rewardManager)), 0);
+  }
+}
+
+contract DestinationRewardManagerNoRecipientSet is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //add funds to the pool to be split among the recipients once registered
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_claimAllRecipientsAfterRecipientsSet() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //try and claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //there should be no rewards claimed as the recipient is not registered
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the recipient received nothing
+      assertEq(getAssetBalance(recipient.addr), 0);
+    }
+
+    //Set the recipients after the rewards have been paid into the pool
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+
+    //claim funds for each recipient within the pool
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //get the recipient that is claiming
+      Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i];
+
+      //there should be no rewards claimed as the recipient is registered
+      claimRewards(PRIMARY_POOL_ARRAY, recipient.addr);
+
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount);
+    }
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol
new file mode 100644
index 00000000000..4c79d2cba5e
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol";
+import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+/**
+ * @title DestinationRewardManagerSetupTest
+ * @author Michael Fletcher
+ * @notice This contract will test the core functionality of the DestinationRewardManager contract
+ */
+contract DestinationRewardManagerSetupTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+  }
+
+  function test_rejectsZeroLinkAddressOnConstruction() public {
+    //should revert if the contract is a zero address
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //create a rewardManager with a zero link address
+    new DestinationRewardManager(address(0));
+  }
+
+  function test_eventEmittedUponFeeManagerUpdate() public {
+    //expect the event to be emitted
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit FeeManagerUpdated(FEE_MANAGER_2);
+
+    //set the verifier proxy
+    setFeeManager(FEE_MANAGER_2, ADMIN);
+  }
+
+  function test_eventEmittedUponFeePaid() public {
+    //create pool and add funds
+    createPrimaryPool();
+
+    //change to the feeManager who is the one who will be paying the fees
+    changePrank(FEE_MANAGER);
+
+    //approve the amount being paid into the pool
+    ERC20Mock(getAsset(POOL_DEPOSIT_AMOUNT).assetAddress).approve(address(rewardManager), POOL_DEPOSIT_AMOUNT);
+
+    IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1);
+    payments[0] = IDestinationRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT));
+
+    //event is emitted when funds are added
+    vm.expectEmit();
+    emit FeePaid(payments, FEE_MANAGER);
+
+    //this represents the verifier adding some funds to the pool
+    rewardManager.onFeePaid(payments, FEE_MANAGER);
+  }
+
+  function test_setFeeManagerZeroAddress() public {
+    //should revert if the contract is a zero address
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //set the verifier proxy
+    setFeeManager(address(0), ADMIN);
+  }
+
+  function test_addFeeManagerZeroAddress() public {
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+    rewardManager.addFeeManager(address(0));
+  }
+
+  function test_addFeeManagerExistingAddress() public {
+    address dummyAddress = address(998);
+    rewardManager.addFeeManager(dummyAddress);
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+    rewardManager.addFeeManager(dummyAddress);
+  }
+
+  function test_removeFeeManagerNonExistentAddress() public {
+    address dummyAddress = address(991);
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+    rewardManager.removeFeeManager(dummyAddress);
+  }
+
+  function test_addRemoveFeeManager() public {
+    address dummyAddress1 = address(1);
+    address dummyAddress2 = address(2);
+    rewardManager.addFeeManager(dummyAddress1);
+    rewardManager.addFeeManager(dummyAddress2);
+    assertEq(rewardManager.s_feeManagerAddressList(dummyAddress1), dummyAddress1);
+    assertEq(rewardManager.s_feeManagerAddressList(dummyAddress2), dummyAddress2);
+    rewardManager.removeFeeManager(dummyAddress1);
+    assertEq(rewardManager.s_feeManagerAddressList(dummyAddress1), address(0));
+    assertEq(rewardManager.s_feeManagerAddressList(dummyAddress2), dummyAddress2);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol
new file mode 100644
index 00000000000..4aa3c868b35
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+/**
+ * @title DestinationRewardManagerPayRecipientsTest
+ * @author Michael Fletcher
+ * @notice This contract will test the payRecipients functionality of the RewardManager contract
+ */
+contract DestinationRewardManagerPayRecipientsTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_payAllRecipients() public {
+    //pay all the recipients in the pool
+    payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), ADMIN);
+
+    //each recipient should receive 1/4 of the pool
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check each recipient received the correct amount
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount);
+    }
+  }
+
+  function test_paySingleRecipient() public {
+    //get the first individual recipient
+    address recipient = getPrimaryRecipientAddresses()[0];
+
+    //get a single recipient as an array
+    address[] memory recipients = new address[](1);
+    recipients[0] = recipient;
+
+    //pay a single recipient
+    payRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //the recipient should have received 1/4 of the deposit amount
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    assertEq(getAssetBalance(recipient), expectedRecipientAmount);
+  }
+
+  function test_payRecipientWithInvalidPool() public {
+    //get the first individual recipient
+    address recipient = getPrimaryRecipientAddresses()[0];
+
+    //get a single recipient as an array
+    address[] memory recipients = new address[](1);
+    recipients[0] = recipient;
+
+    //pay a single recipient
+    payRecipients(SECONDARY_POOL_ID, recipients, ADMIN);
+
+    //the recipient should have received nothing
+    assertEq(getAssetBalance(recipient), 0);
+  }
+
+  function test_payRecipientsEmptyRecipientList() public {
+    //get a single recipient
+    address[] memory recipients = new address[](0);
+
+    //pay a single recipient
+    payRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //rewardManager should have the full balance
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_payAllRecipientsWithAdditionalUnregisteredRecipient() public {
+    //load all the recipients and add an additional one who is not in the pool
+    address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1);
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      recipients[i] = getPrimaryRecipientAddresses()[i];
+    }
+    recipients[recipients.length - 1] = DEFAULT_RECIPIENT_5;
+
+    //pay the recipients
+    payRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //each recipient should receive 1/4 of the pool except the last
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check each recipient received the correct amount
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount);
+    }
+
+    //the unregistered recipient should receive nothing
+    assertEq(getAssetBalance(DEFAULT_RECIPIENT_5), 0);
+  }
+
+  function test_payAllRecipientsWithAdditionalInvalidRecipient() public {
+    //load all the recipients and add an additional one which is invalid, that should receive nothing
+    address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1);
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      recipients[i] = getPrimaryRecipientAddresses()[i];
+    }
+    recipients[recipients.length - 1] = INVALID_ADDRESS;
+
+    //pay the recipients
+    payRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //each recipient should receive 1/4 of the pool except the last
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check each recipient received the correct amount
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount);
+    }
+  }
+
+  function test_paySubsetOfRecipientsInPool() public {
+    //load a subset of the recipients into an array
+    address[] memory recipients = new address[](getPrimaryRecipientAddresses().length - 1);
+    for (uint256 i = 0; i < recipients.length; i++) {
+      recipients[i] = getPrimaryRecipientAddresses()[i];
+    }
+
+    //pay the subset of recipients
+    payRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //each recipient should receive 1/4 of the pool except the last
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check each subset of recipients received the correct amount
+    for (uint256 i = 0; i < recipients.length - 1; i++) {
+      assertEq(getAssetBalance(recipients[i]), expectedRecipientAmount);
+    }
+
+    //check the pool has the remaining balance
+    assertEq(
+      getAssetBalance(address(rewardManager)),
+      POOL_DEPOSIT_AMOUNT - expectedRecipientAmount * recipients.length
+    );
+  }
+
+  function test_payAllRecipientsFromNonAdminUser() public {
+    //should revert if the caller isn't an admin or recipient within the pool
+    vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR);
+
+    //pay all the recipients in the pool
+    payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), FEE_MANAGER);
+  }
+
+  function test_payAllRecipientsFromRecipientInPool() public {
+    //pay all the recipients in the pool
+    payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), DEFAULT_RECIPIENT_1);
+
+    //each recipient should receive 1/4 of the pool
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //check each recipient received the correct amount
+    for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) {
+      assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount);
+    }
+  }
+
+  function test_payRecipientsWithInvalidPoolId() public {
+    //pay all the recipients in the pool
+    payRecipients(INVALID_POOL_ID, getPrimaryRecipientAddresses(), ADMIN);
+
+    //pool should still contain the full balance
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_addFundsToPoolAsOwner() public {
+    //add funds to the pool
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_addFundsToPoolAsNonOwnerOrFeeManager() public {
+    //should revert if the caller isn't an admin or recipient within the pool
+    vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR);
+
+    IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1);
+    payments[0] = IDestinationRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT));
+
+    //add funds to the pool
+    rewardManager.onFeePaid(payments, USER);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol
new file mode 100644
index 00000000000..facbaa1ab77
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+/**
+ * @title DestinationRewardManagerSetRecipientsTest
+ * @author Michael Fletcher
+ * @notice This contract will test the setRecipient functionality of the RewardManager contract
+ */
+contract DestinationRewardManagerSetRecipientsTest is BaseDestinationRewardManagerTest {
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+  }
+
+  function test_setRewardRecipients() public {
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+  }
+
+  function test_setRewardRecipientsIsEmpty() public {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+
+    //should revert if the recipients array is empty
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_setRewardRecipientWithZeroWeight() public {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5);
+
+    //init each recipient with even weights
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 25);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 25);
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, 0);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_setRewardRecipientWithZeroAddress() public {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = getPrimaryRecipients();
+
+    //override the first recipient with a zero address
+    recipients[0].addr = address(0);
+
+    //should revert if the recipients array is empty
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_setRewardRecipientWeights() public {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+
+    //init each recipient with even weights
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 25);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 25);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, 25);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, 25);
+
+    //should revert if the recipients array is empty
+    vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR);
+
+    //set the recipients with a recipient with a weight of 100%
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_setSingleRewardRecipient() public {
+    //array of recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](1);
+
+    //init each recipient with even weights
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR);
+
+    //set the recipients with a recipient with a weight of 100%
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_setRewardRecipientTwice() public {
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+
+    //should revert if recipients for this pool have already been set
+    vm.expectRevert(INVALID_POOL_ID_ERROR_SELECTOR);
+
+    //set the recipients again
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+  }
+
+  function test_setRewardRecipientFromNonOwnerOrFeeManagerAddress() public {
+    //should revert if the sender is not the owner or proxy
+    vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), USER);
+  }
+
+  function test_setRewardRecipientFromManagerAddress() public {
+    //update the proxy address
+    setFeeManager(FEE_MANAGER_2, ADMIN);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER_2);
+  }
+
+  function test_eventIsEmittedUponSetRecipients() public {
+    //expect an emit
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients());
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+  }
+
+  function test_setRecipientContainsDuplicateRecipients() public {
+    //create a new array to hold the existing recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2);
+
+    //add all the existing recipients
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      recipients[i] = getPrimaryRecipients()[i];
+    }
+    //add all the existing recipients again
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i];
+    }
+
+    //should revert as the list contains a duplicate
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //set the recipients
+    setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol
new file mode 100644
index 00000000000..226be8ed325
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+/**
+ * @title DestinationRewardManagerUpdateRewardRecipientsTest
+ * @author Michael Fletcher
+ * @notice This contract will test the updateRecipient functionality of the RewardManager contract
+ */
+contract DestinationRewardManagerUpdateRewardRecipientsTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function test_onlyAdminCanUpdateRecipients() public {
+    //should revert if the caller is not the admin
+    vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR);
+
+    //updating a recipient should force the funds to be paid out
+    updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER);
+  }
+
+  function test_updateAllRecipientsWithSameAddressAndWeight() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //updating a recipient should force the funds to be paid out
+    updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount);
+    }
+  }
+
+  function test_updatePartialRecipientsWithSameAddressAndWeight() public {
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //get a subset of the recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25);
+
+    //updating a recipient should force the funds to be paid out
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < recipients.length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount);
+    }
+
+    //the reward manager should still have half remaining funds
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2);
+  }
+
+  function test_updateRecipientWithNewZeroAddress() public {
+    //create a new array to hold the existing recipients plus a new zero address
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 1);
+
+    //add all the existing recipients
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      recipients[i] = getPrimaryRecipients()[i];
+    }
+    //add a new address to the primary recipients
+    recipients[recipients.length - 1] = Common.AddressAndWeight(address(0), 0);
+
+    //should revert if the recipient is a zero address
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //update the recipients with invalid address
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsContainsDuplicateRecipients() public {
+    //create a new array to hold the existing recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2);
+
+    //add all the existing recipients
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      recipients[i] = getPrimaryRecipients()[i];
+    }
+    //add all the existing recipients again
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i];
+    }
+
+    //should revert as the list contains a duplicate
+    vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR);
+
+    //update the recipients with the duplicate addresses
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsToDifferentSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 4);
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //copy the recipient and set the weight to 0 which implies the recipient is being replaced
+      recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0);
+    }
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, ONE_PERCENT * 25);
+    recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, ONE_PERCENT * 25);
+    recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, ONE_PERCENT * 25);
+    recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, ONE_PERCENT * 25);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsToDifferentPartialSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2);
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //copy the recipient and set the weight to 0 which implies the recipient is being replaced
+      recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0);
+    }
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, FIFTY_PERCENT);
+    recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, FIFTY_PERCENT);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsToDifferentLargerSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 5);
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //copy the recipient and set the weight to 0 which implies the recipient is being replaced
+      recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0);
+    }
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 2);
+    recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT * 2);
+    recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT * 2);
+    recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT * 2);
+    recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT * 2);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsUpdateAndRemoveExistingForLargerSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](9);
+
+    //update the existing recipients
+    recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0);
+    recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0);
+    recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3);
+    recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 3);
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT);
+    recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT);
+    recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT);
+    recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT);
+    recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT);
+
+    //should revert as the weight does not equal 100%
+    vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsUpdateAndRemoveExistingForSmallerSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5);
+
+    //update the existing recipients
+    recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0);
+    recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0);
+    recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3);
+    recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 2);
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientsToDifferentSetWithInvalidWeights() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2);
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //copy the recipient and set the weight to 0 which implies the recipient is being replaced
+      recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0);
+    }
+
+    //add the new recipients individually
+    recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5);
+    recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT);
+
+    //should revert as the weight will not equal 100%
+    vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updatePartialRecipientsToSubset() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 0);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 0);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 5);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updatePartialRecipientsWithUnderWeightSet() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT);
+
+    //should revert as the new weights exceed the previous weights being replaced
+    vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updatePartialRecipientsWithExcessiveWeight() public {
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR);
+
+    //should revert as the new weights exceed the previous weights being replaced
+    vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+  }
+
+  function test_updateRecipientWeights() public {
+    //expected recipient amount is 1/4 of the pool deposit for original recipients
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //create a list of containing recipients from the primary configured set with their new weights
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 3);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < recipients.length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount);
+    }
+
+    //the reward manager should have no funds remaining
+    assertEq(getAssetBalance(address(rewardManager)), 0);
+
+    //add more funds to the pool to check new distribution
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //loop each user and claim the rewards
+    for (uint256 i; i < recipients.length; i++) {
+      //claim the rewards for this recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr);
+    }
+
+    //manually check the balance of each recipient
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_1),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_2),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_3),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 3) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_4),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 5) / POOL_SCALAR + expectedRecipientAmount
+    );
+  }
+
+  function test_partialUpdateRecipientWeights() public {
+    //expected recipient amount is 1/4 of the pool deposit for original recipients
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //create a list of containing recipients from the primary configured set with their new weights
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < recipients.length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount);
+    }
+
+    //the reward manager should have half the funds remaining
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2);
+
+    //add more funds to the pool to check new distribution
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //loop each user and claim the rewards
+    for (uint256 i; i < recipients.length; i++) {
+      //claim the rewards for this recipient
+      claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr);
+    }
+
+    //manually check the balance of each recipient
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_1),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(DEFAULT_RECIPIENT_2),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount
+    );
+
+    //the reward manager should have half the funds remaining
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+  }
+
+  function test_eventIsEmittedUponUpdateRecipients() public {
+    //expect an emit
+    vm.expectEmit();
+
+    //emit the event that is expected to be emitted
+    emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients());
+
+    //expected recipient amount is 1/4 of the pool deposit
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //updating a recipient should force the funds to be paid out
+    updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < getPrimaryRecipients().length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount);
+    }
+  }
+}
+
+contract DestinationRewardManagerUpdateRewardRecipientsMultiplePoolsTest is BaseDestinationRewardManagerTest {
+  uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18;
+
+  function setUp() public override {
+    //setup contracts
+    super.setUp();
+
+    //create a single pool for these tests
+    createPrimaryPool();
+    createSecondaryPool();
+
+    //add funds to the pool to be split among the recipients
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+    addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+  }
+
+  function getSecondaryRecipients() public override returns (Common.AddressAndWeight[] memory) {
+    //for testing purposes, the primary and secondary pool to contain the same recipients
+    return getPrimaryRecipients();
+  }
+
+  function test_updatePrimaryRecipientWeights() public {
+    //expected recipient amount is 1/4 of the pool deposit for original recipients
+    uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4;
+
+    //create a list of containing recipients from the primary configured set, and new recipients
+    Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4);
+    recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT * 4);
+    recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4);
+    recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT);
+    recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT);
+
+    //updating a recipient should force the funds to be paid out for the primary recipients
+    updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN);
+
+    //check each recipient received the correct amount
+    for (uint256 i; i < recipients.length; i++) {
+      //check the balance matches the ratio the recipient should have received
+      assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount);
+    }
+
+    //the reward manager should still have the funds for the secondary pool
+    assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT);
+
+    //add more funds to the pool to check new distribution
+    addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER);
+
+    //claim the rewards for the updated recipients manually
+    claimRewards(PRIMARY_POOL_ARRAY, recipients[0].addr);
+    claimRewards(PRIMARY_POOL_ARRAY, recipients[1].addr);
+    claimRewards(PRIMARY_POOL_ARRAY, recipients[2].addr);
+    claimRewards(PRIMARY_POOL_ARRAY, recipients[3].addr);
+
+    //check the balance matches the ratio the recipient who were updated should have received
+    assertEq(
+      getAssetBalance(recipients[0].addr),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(recipients[1].addr),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(recipients[2].addr),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount
+    );
+    assertEq(
+      getAssetBalance(recipients[3].addr),
+      (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount
+    );
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol
new file mode 100644
index 00000000000..ec3b3a0eed7
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Test} from "forge-std/Test.sol";
+import {DestinationVerifierProxy} from "../../DestinationVerifierProxy.sol";
+import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+import {IDestinationVerifier} from "../../interfaces/IDestinationVerifier.sol";
+import {IDestinationVerifierProxy} from "../../interfaces/IDestinationVerifierProxy.sol";
+import {DestinationVerifier} from "../../DestinationVerifier.sol";
+import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {DestinationFeeManager} from "../../DestinationFeeManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";
+import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol";
+import {DestinationRewardManager} from "../../DestinationRewardManager.sol";
+import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol";
+
+contract BaseTest is Test {
+  uint64 internal constant POOL_SCALAR = 1e18;
+  uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100;
+  uint256 internal constant MAX_ORACLES = 31;
+  address internal constant ADMIN = address(1);
+  address internal constant USER = address(2);
+
+  address internal constant MOCK_VERIFIER_ADDRESS = address(100);
+  address internal constant ACCESS_CONTROLLER_ADDRESS = address(300);
+
+  uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10;
+  uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12;
+
+  uint64 internal constant VERIFIER_VERSION = 1;
+
+  uint8 internal constant FAULT_TOLERANCE = 10;
+
+  DestinationVerifierProxy internal s_verifierProxy;
+  DestinationVerifier internal s_verifier;
+  DestinationFeeManager internal feeManager;
+  DestinationRewardManager internal rewardManager;
+  ERC20Mock internal link;
+  WERC20Mock internal native;
+
+  struct Signer {
+    uint256 mockPrivateKey;
+    address signerAddress;
+  }
+
+  Signer[MAX_ORACLES] internal s_signers;
+  bytes32[] internal s_offchaintransmitters;
+  bool private s_baseTestInitialized;
+
+  struct V3Report {
+    // The feed ID the report has data for
+    bytes32 feedId;
+    // The time the median value was observed on
+    uint32 observationsTimestamp;
+    // The timestamp the report is valid from
+    uint32 validFromTimestamp;
+    // The link fee
+    uint192 linkFee;
+    // The native fee
+    uint192 nativeFee;
+    // The expiry of the report
+    uint32 expiresAt;
+    // The median value agreed in an OCR round
+    int192 benchmarkPrice;
+    // The best bid value agreed in an OCR round
+    int192 bid;
+    // The best ask value agreed in an OCR round
+    int192 ask;
+  }
+
+  bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
+  bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000;
+  bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000;
+  bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000;
+
+  bytes32 internal constant INVALID_FEED = keccak256("INVALID");
+  uint32 internal constant OBSERVATIONS_TIMESTAMP = 1000;
+  uint64 internal constant BLOCKNUMBER_LOWER_BOUND = 1000;
+  uint64 internal constant BLOCKNUMBER_UPPER_BOUND = BLOCKNUMBER_LOWER_BOUND + 5;
+  int192 internal constant MEDIAN = 1 ether;
+  int192 internal constant BID = 500000000 gwei;
+  int192 internal constant ASK = 2 ether;
+
+  //version 0 feeds
+  bytes32 internal constant FEED_ID = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK;
+  bytes32 internal constant FEED_ID_2 = (keccak256("LINK-USD") & V_MASK) | V1_BITMASK;
+  bytes32 internal constant FEED_ID_3 = (keccak256("BTC-USD") & V_MASK) | V1_BITMASK;
+
+  //version 3 feeds
+  bytes32 internal constant FEED_ID_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK;
+
+  function _encodeReport(V3Report memory report) internal pure returns (bytes memory) {
+    return
+      abi.encode(
+        report.feedId,
+        report.observationsTimestamp,
+        report.validFromTimestamp,
+        report.nativeFee,
+        report.linkFee,
+        report.expiresAt,
+        report.benchmarkPrice,
+        report.bid,
+        report.ask
+      );
+  }
+
+  function _generateSignerSignatures(
+    bytes memory report,
+    bytes32[3] memory reportContext,
+    Signer[] memory signers
+  ) internal pure returns (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) {
+    bytes32[] memory rs = new bytes32[](signers.length);
+    bytes32[] memory ss = new bytes32[](signers.length);
+    bytes memory vs = new bytes(signers.length);
+
+    bytes32 hash = keccak256(abi.encodePacked(keccak256(report), reportContext));
+
+    for (uint256 i = 0; i < signers.length; i++) {
+      (uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i].mockPrivateKey, hash);
+      rs[i] = r;
+      ss[i] = s;
+      vs[i] = bytes1(v - 27);
+    }
+    return (rs, ss, bytes32(vs));
+  }
+
+  function _generateV3EncodedBlob(
+    V3Report memory report,
+    bytes32[3] memory reportContext,
+    Signer[] memory signers
+  ) internal pure returns (bytes memory) {
+    bytes memory reportBytes = _encodeReport(report);
+    (bytes32[] memory rs, bytes32[] memory ss, bytes32 rawVs) = _generateSignerSignatures(
+      reportBytes,
+      reportContext,
+      signers
+    );
+    return abi.encode(reportContext, reportBytes, rs, ss, rawVs);
+  }
+
+  function _verify(bytes memory payload, address feeAddress, uint256 wrappedNativeValue, address sender) internal {
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    s_verifierProxy.verify{value: wrappedNativeValue}(payload, abi.encode(feeAddress));
+
+    changePrank(originalAddr);
+  }
+
+  function _generateV3Report() internal view returns (V3Report memory) {
+    return
+      V3Report({
+        feedId: FEED_ID_V3,
+        observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+        validFromTimestamp: uint32(block.timestamp),
+        nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+        linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+        expiresAt: uint32(block.timestamp),
+        benchmarkPrice: MEDIAN,
+        bid: BID,
+        ask: ASK
+      });
+  }
+
+  function _verifyBulk(
+    bytes[] memory payload,
+    address feeAddress,
+    uint256 wrappedNativeValue,
+    address sender
+  ) internal {
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    s_verifierProxy.verifyBulk{value: wrappedNativeValue}(payload, abi.encode(feeAddress));
+
+    changePrank(originalAddr);
+  }
+
+  function _approveLink(address spender, uint256 quantity, address sender) internal {
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    link.approve(spender, quantity);
+    changePrank(originalAddr);
+  }
+
+  function setUp() public virtual {
+    // BaseTest.setUp is often called multiple times from tests' setUp due to inheritance.
+    if (s_baseTestInitialized) return;
+    s_baseTestInitialized = true;
+    vm.startPrank(ADMIN);
+
+    s_verifierProxy = new DestinationVerifierProxy();
+    s_verifier = new DestinationVerifier(address(s_verifierProxy));
+    s_verifierProxy.setVerifier(address(s_verifier));
+
+    // setting up FeeManager and RewardManager
+    native = new WERC20Mock();
+    link = new ERC20Mock("LINK", "LINK", ADMIN, 0);
+    rewardManager = new DestinationRewardManager(address(link));
+    feeManager = new DestinationFeeManager(address(link), address(native), address(s_verifier), address(rewardManager));
+
+    for (uint256 i; i < MAX_ORACLES; i++) {
+      uint256 mockPK = i + 1;
+      s_signers[i].mockPrivateKey = mockPK;
+      s_signers[i].signerAddress = vm.addr(mockPK);
+    }
+  }
+
+  function _getSigners(uint256 numSigners) internal view returns (Signer[] memory) {
+    Signer[] memory signers = new Signer[](numSigners);
+    for (uint256 i; i < numSigners; i++) {
+      signers[i] = s_signers[i];
+    }
+    return signers;
+  }
+
+  function _getSignerAddresses(Signer[] memory signers) internal pure returns (address[] memory) {
+    address[] memory signerAddrs = new address[](signers.length);
+    for (uint256 i = 0; i < signerAddrs.length; i++) {
+      signerAddrs[i] = signers[i].signerAddress;
+    }
+    return signerAddrs;
+  }
+
+  function _signerAddressAndDonConfigKey(address signer, bytes24 donConfigId) internal pure returns (bytes32) {
+    return keccak256(abi.encodePacked(signer, donConfigId));
+  }
+
+  function _donConfigIdFromConfigData(address[] memory signers, uint8 f) internal pure returns (bytes24) {
+    Common._quickSort(signers, 0, int256(signers.length - 1));
+    bytes24 donConfigId = bytes24(keccak256(abi.encodePacked(signers, f)));
+    return donConfigId;
+  }
+
+  function assertReportsEqual(bytes memory response, V3Report memory testReport) public pure {
+    (
+      bytes32 feedId,
+      uint32 observationsTimestamp,
+      uint32 validFromTimestamp,
+      uint192 nativeFee,
+      uint192 linkFee,
+      uint32 expiresAt,
+      int192 benchmarkPrice,
+      int192 bid,
+      int192 ask
+    ) = abi.decode(response, (bytes32, uint32, uint32, uint192, uint192, uint32, int192, int192, int192));
+    assertEq(feedId, testReport.feedId);
+    assertEq(observationsTimestamp, testReport.observationsTimestamp);
+    assertEq(validFromTimestamp, testReport.validFromTimestamp);
+    assertEq(expiresAt, testReport.expiresAt);
+    assertEq(benchmarkPrice, testReport.benchmarkPrice);
+    assertEq(bid, testReport.bid);
+    assertEq(ask, testReport.ask);
+    assertEq(linkFee, testReport.linkFee);
+    assertEq(nativeFee, testReport.nativeFee);
+  }
+
+  function _approveNative(address spender, uint256 quantity, address sender) internal {
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    native.approve(spender, quantity);
+    changePrank(originalAddr);
+  }
+}
+
+contract VerifierWithFeeManager is BaseTest {
+  uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether;
+  uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether;
+
+  function setUp() public virtual override {
+    BaseTest.setUp();
+
+    s_verifierProxy.setVerifier(address(s_verifier));
+    s_verifier.setFeeManager(address(feeManager));
+    rewardManager.addFeeManager(address(feeManager));
+
+    //mint some tokens to the user
+    link.mint(USER, DEFAULT_LINK_MINT_QUANTITY);
+    native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+    vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+
+    //mint some link tokens to the feeManager pool
+    link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+  }
+}
+
+contract MultipleVerifierWithMultipleFeeManagers is BaseTest {
+  uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether;
+  uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether;
+
+  DestinationVerifier internal s_verifier2;
+  DestinationVerifier internal s_verifier3;
+
+  DestinationVerifierProxy internal s_verifierProxy2;
+  DestinationVerifierProxy internal s_verifierProxy3;
+
+  DestinationFeeManager internal feeManager2;
+
+  function setUp() public virtual override {
+    /*
+      - Sets up 3 verifiers
+      - Sets up 2 Fee managers, wire the fee managers and verifiers
+      - Sets up a Reward Manager which can be used by both fee managers
+     */
+    BaseTest.setUp();
+
+    s_verifierProxy2 = new DestinationVerifierProxy();
+    s_verifierProxy3 = new DestinationVerifierProxy();
+
+    s_verifier2 = new DestinationVerifier(address(s_verifierProxy2));
+    s_verifier3 = new DestinationVerifier(address(s_verifierProxy3));
+
+    s_verifierProxy2.setVerifier(address(s_verifier2));
+    s_verifierProxy3.setVerifier(address(s_verifier3));
+
+    feeManager2 = new DestinationFeeManager(
+      address(link),
+      address(native),
+      address(s_verifier),
+      address(rewardManager)
+    );
+
+    s_verifier.setFeeManager(address(feeManager));
+    s_verifier2.setFeeManager(address(feeManager));
+    s_verifier3.setFeeManager(address(feeManager2));
+
+    // this is already set in the base contract
+    // feeManager.addVerifier(address(s_verifier));
+    feeManager.addVerifier(address(s_verifier2));
+    feeManager2.addVerifier(address(s_verifier3));
+
+    rewardManager.addFeeManager(address(feeManager));
+    rewardManager.addFeeManager(address(feeManager2));
+
+    //mint some tokens to the user
+    link.mint(USER, DEFAULT_LINK_MINT_QUANTITY);
+    native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+    vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY);
+
+    //mint some link tokens to the feeManager pool
+    link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol
new file mode 100644
index 00000000000..d4772ba1855
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {Test} from "forge-std/Test.sol";
+import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {IDestinationFeeManager} from "../../../v0.4.0/interfaces/IDestinationFeeManager.sol";
+import {IDestinationRewardManager} from "../../../v0.4.0/interfaces/IDestinationRewardManager.sol";
+import {IDestinationVerifierProxy} from "../../../v0.4.0/interfaces/IDestinationVerifierProxy.sol";
+import {Common} from "../../../libraries/Common.sol";
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
+
+/*
+This test checks the interfaces of destination verifier matches the expectations.
+The code here comes from this example:
+
+https://docs.chain.link/chainlink-automation/guides/streams-lookup
+
+*/
+
+// Custom interfaces for IVerifierProxy and IFeeManager
+interface IVerifierProxy {
+  /**
+   * @notice Verifies that the data encoded has been signed.
+   * correctly by routing to the correct verifier, and bills the user if applicable.
+   * @param payload The encoded data to be verified, including the signed
+   * report.
+   * @param parameterPayload Fee metadata for billing. For the current implementation this is just the abi-encoded fee token ERC-20 address.
+   * @return verifierResponse The encoded report from the verifier.
+   */
+  function verify(
+    bytes calldata payload,
+    bytes calldata parameterPayload
+  ) external payable returns (bytes memory verifierResponse);
+
+  function s_feeManager() external view returns (IDestinationFeeManager);
+}
+
+interface IFeeManager {
+  /**
+   * @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers.
+   * This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses.
+   * @param subscriber The address attempting to verify the report. A discount is applied if this address
+   * is recognized as a subscriber.
+   * @param unverifiedReport The report data awaiting verification. The content of this report is used to
+   * determine the base fee and reward, before considering subscriber discounts.
+   * @param quoteAddress The payment token address used for quoting fees and rewards.
+   * @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable.
+   * @return reward The reward allocated to the caller for successfully verifying the report.
+   * @return totalDiscount The total discount amount deducted from the fee for subscribers.
+   */
+  function getFeeAndReward(
+    address subscriber,
+    bytes memory unverifiedReport,
+    address quoteAddress
+  ) external returns (Common.Asset memory, Common.Asset memory, uint256);
+
+  function i_linkAddress() external view returns (address);
+
+  function i_nativeAddress() external view returns (address);
+
+  function i_rewardManager() external view returns (address);
+}
+
+//Tests
+// https://docs.chain.link/chainlink-automation/guides/streams-lookup
+contract VerifierInterfacesTest is VerifierWithFeeManager {
+  address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1"))));
+
+  IVerifierProxy public verifier;
+  V3Report internal s_testReport;
+
+  address public FEE_ADDRESS;
+  string public constant DATASTREAMS_FEEDLABEL = "feedIDs";
+  string public constant DATASTREAMS_QUERYLABEL = "timestamp";
+  int192 public last_retrieved_price;
+  bytes internal signedReport;
+  bytes32[3] internal s_reportContext;
+  uint8 MINIMAL_FAULT_TOLERANCE = 2;
+
+  function setUp() public virtual override {
+    VerifierWithFeeManager.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    s_testReport = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1);
+    weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100);
+    s_verifier.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights);
+    signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers);
+
+    verifier = IVerifierProxy(address(s_verifierProxy));
+  }
+
+  function test_DestinationContractInterfaces() public {
+    bytes memory unverifiedReport = signedReport;
+
+    (, bytes memory reportData) = abi.decode(unverifiedReport, (bytes32[3], bytes));
+
+    // Report verification fees
+    IFeeManager feeManager = IFeeManager(address(verifier.s_feeManager()));
+    IDestinationRewardManager rewardManager = IDestinationRewardManager(address(feeManager.i_rewardManager()));
+
+    address feeTokenAddress = feeManager.i_linkAddress();
+    (Common.Asset memory fee, , ) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress);
+
+    // Approve rewardManager to spend this contract's balance in fees
+    _approveLink(address(rewardManager), fee.amount, USER);
+    _verify(unverifiedReport, address(feeTokenAddress), 0, USER);
+
+    assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - fee.amount);
+    assertEq(link.balanceOf(address(rewardManager)), fee.amount);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol
new file mode 100644
index 00000000000..c93c9dc6d9a
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationFeeManager} from "../../../v0.4.0/DestinationFeeManager.sol";
+import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
+
+contract DestinationVerifierProxyInitializeVerifierTest is BaseTest {
+  function test_setVerifierCalledByNoOwner() public {
+    address STRANGER = address(999);
+    changePrank(STRANGER);
+    vm.expectRevert(bytes("Only callable by owner"));
+    s_verifierProxy.setVerifier(address(s_verifier));
+  }
+
+  function test_setVerifierWhichDoesntHonourInterface() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifierProxy.VerifierInvalid.selector, address(rewardManager)));
+    s_verifierProxy.setVerifier(address(rewardManager));
+  }
+
+  function test_setVerifierOk() public {
+    s_verifierProxy.setVerifier(address(s_verifier));
+    assertEq(s_verifierProxy.s_feeManager(), s_verifier.s_feeManager());
+    assertEq(s_verifierProxy.s_accessController(), s_verifier.s_accessController());
+  }
+
+  function test_correctlySetsTheOwner() public {
+    DestinationVerifierProxy proxy = new DestinationVerifierProxy();
+    assertEq(proxy.owner(), ADMIN);
+  }
+
+  function test_correctlySetsVersion() public view {
+    string memory version = s_verifierProxy.typeAndVersion();
+    assertEq(version, "DestinationVerifierProxy 1.0.0");
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol
new file mode 100644
index 00000000000..6309efc9959
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract DestinationVerifierSetConfigTest is BaseTest {
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReport;
+
+  function setUp() public virtual override {
+    BaseTest.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+  }
+
+  function test_removeLatestConfigWhenNoConfigShouldFail() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector));
+    s_verifier.removeLatestConfig();
+  }
+
+  function test_removeLatestConfig() public {
+    /*
+       This test sets two Configs: Config A and Config B.
+       - it removes and readds config B multiple times while trying Config A verifications
+      */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersA = new BaseTest.Signer[](7);
+    signersA[0] = signers[0];
+    signersA[1] = signers[1];
+    signersA[2] = signers[2];
+    signersA[3] = signers[3];
+    signersA[4] = signers[4];
+    signersA[5] = signers[5];
+    signersA[6] = signers[6];
+
+    // ConfigA
+    address[] memory signersAddrA = _getSignerAddresses(signersA);
+    s_verifier.setConfig(signersAddrA, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+    vm.warp(block.timestamp + 10);
+    V3Report memory s_testReportA = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    vm.warp(block.timestamp + 100);
+    // Config B
+    BaseTest.Signer[] memory signersB = new BaseTest.Signer[](7);
+    // signers in ConfigA
+    signersB[0] = signers[8];
+    signersB[1] = signers[9];
+    signersB[2] = signers[10];
+    signersB[3] = signers[11];
+    signersB[4] = signers[12];
+    signersB[5] = signers[13];
+    signersB[6] = signers[14];
+    address[] memory signersAddrsB = _getSignerAddresses(signersB);
+    s_verifier.setConfig(signersAddrsB, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory s_testReportB = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSignersA = new BaseTest.Signer[](3);
+    reportSignersA[0] = signers[0];
+    reportSignersA[1] = signers[1];
+    reportSignersA[2] = signers[2];
+
+    BaseTest.Signer[] memory reportSignersB = new BaseTest.Signer[](3);
+    reportSignersB[0] = signers[8];
+    reportSignersB[1] = signers[9];
+    reportSignersB[2] = signers[10];
+
+    bytes memory signedReportA = _generateV3EncodedBlob(s_testReportA, s_reportContext, reportSignersA);
+    bytes memory signedReportB = _generateV3EncodedBlob(s_testReportB, s_reportContext, reportSignersB);
+
+    // verifying should work
+    s_verifierProxy.verify(signedReportA, abi.encode(native));
+    s_verifierProxy.verify(signedReportB, abi.encode(native));
+
+    s_verifier.removeLatestConfig();
+
+    // this should remove the latest config, so ConfigA should be able to verify reports still
+    s_verifierProxy.verify(signedReportA, abi.encode(native));
+    // this report cannot be verified any longer because ConfigB is not there
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReportB, abi.encode(native));
+
+    // since ConfigB is removed we should be able to set it again with no errors
+    s_verifier.setConfig(signersAddrsB, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    // we should be able to remove ConfigB
+    s_verifier.removeLatestConfig();
+    // removing configA
+    s_verifier.removeLatestConfig();
+
+    // verifigny should fail
+    // verifying should work
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReportA, abi.encode(native));
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReportB, abi.encode(native));
+
+    // removing again should fail. no other configs exist
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector));
+    s_verifier.removeLatestConfig();
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol
new file mode 100644
index 00000000000..d40b674f237
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+
+contract DestinationVerifierSetAccessControllerTest is BaseTest {
+  event AccessControllerSet(address oldAccessController, address newAccessController);
+
+  function test_revertsIfCalledByNonOwner() public {
+    vm.expectRevert("Only callable by owner");
+
+    changePrank(USER);
+    s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS);
+  }
+
+  function test_successfullySetsNewAccessController() public {
+    s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS);
+    address ac = s_verifier.s_accessController();
+    assertEq(ac, ACCESS_CONTROLLER_ADDRESS);
+  }
+
+  function test_successfullySetsNewAccessControllerIsEmpty() public {
+    s_verifier.setAccessController(address(0));
+    address ac = s_verifier.s_accessController();
+    assertEq(ac, address(0));
+  }
+
+  function test_emitsTheCorrectEvent() public {
+    vm.expectEmit(true, false, false, false);
+    emit AccessControllerSet(address(0), ACCESS_CONTROLLER_ADDRESS);
+    s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol
new file mode 100644
index 00000000000..f6e5fd1f213
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract DestinationVerifierSetConfigTest is BaseTest {
+  function setUp() public virtual override {
+    BaseTest.setUp();
+  }
+
+  function test_revertsIfCalledByNonOwner() public {
+    vm.expectRevert("Only callable by owner");
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    changePrank(USER);
+    s_verifier.setConfig(_getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_revertsIfSetWithTooManySigners() public {
+    address[] memory signers = new address[](MAX_ORACLES + 1);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ExcessSigners.selector, signers.length, MAX_ORACLES));
+    s_verifier.setConfig(signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_revertsIfFaultToleranceIsZero() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.FaultToleranceMustBePositive.selector));
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    s_verifier.setConfig(_getSignerAddresses(signers), 0, new Common.AddressAndWeight[](0));
+  }
+
+  function test_revertsIfNotEnoughSigners() public {
+    address[] memory signers = new address[](2);
+    signers[0] = address(1000);
+    signers[1] = address(1001);
+
+    vm.expectRevert(
+      abi.encodeWithSelector(DestinationVerifier.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1)
+    );
+    s_verifier.setConfig(signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_revertsIfDuplicateSigners() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    signerAddrs[0] = signerAddrs[1];
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.NonUniqueSignatures.selector));
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_revertsIfSignerContainsZeroAddress() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    signerAddrs[0] = address(0);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ZeroAddress.selector));
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_donConfigIdIsSameForSignersInDifferentOrder() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+
+    bytes24 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    address temp = signerAddrs[0];
+    signerAddrs[0] = signerAddrs[1];
+    signerAddrs[1] = temp;
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigAlreadyExists.selector, expectedDonConfigId));
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+  }
+
+  function test_NoDonConfigAlreadyExists() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    // testing adding same set of Signers but different FAULT_TOLERENCE does not result in DonConfigAlreadyExists revert
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0));
+
+    // testing adding a different set of Signers with same FAULT_TOLERENCE does not result in DonConfigAlreadyExists revert
+    address[] memory signerAddrsMinusOne = new address[](signerAddrs.length - 1);
+    for (uint256 i = 0; i < signerAddrs.length - 1; i++) {
+      signerAddrsMinusOne[i] = signerAddrs[i];
+    }
+    s_verifier.setConfig(signerAddrsMinusOne, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0));
+  }
+
+  function test_addressesAndWeightsDoNotProduceSideEffectsInDonConfigIds() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    bytes24 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigAlreadyExists.selector, expectedDonConfigId));
+
+    // Same call to setConfig with different addressAndWeights do not entail a new DonConfigID
+    // Resulting in a DonConfigAlreadyExists error
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1);
+    weights[0] = Common.AddressAndWeight(signers[0].signerAddress, 1);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+  }
+
+  function test_setConfigActiveUnknownDonConfigId() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector));
+    s_verifier.setConfigActive(3, true);
+  }
+
+  function test_setConfigWithActivationTime() public {
+    // simple case setting a config with specific activation time
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    uint32 activationTime = 10;
+    s_verifier.setConfigWithActivationTime(
+      signerAddrs,
+      FAULT_TOLERANCE,
+      new Common.AddressAndWeight[](0),
+      activationTime
+    );
+  }
+
+  function test_setConfigWithActivationTimeNoFutureTimeShouldFail() public {
+    // calling setConfigWithActivationTime with a future timestamp should fail
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    uint32 activationTime = uint32(block.timestamp) + 100;
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadActivationTime.selector));
+    s_verifier.setConfigWithActivationTime(
+      signerAddrs,
+      FAULT_TOLERANCE,
+      new Common.AddressAndWeight[](0),
+      activationTime
+    );
+  }
+
+  function test_setConfigWithActivationTimeEarlierThanLatestConfigShouldFail() public {
+    // setting a config older than the latest current config should fail
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    uint32 oldActivationTime = uint32(block.timestamp) - 1;
+    // sets a config with timestamp = block.timestamp
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+    // setting a config with ealier timestamp retuls in failure
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadActivationTime.selector));
+    s_verifier.setConfigWithActivationTime(
+      signerAddrs,
+      FAULT_TOLERANCE - 1,
+      new Common.AddressAndWeight[](0),
+      oldActivationTime
+    );
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol
new file mode 100644
index 00000000000..fdf75d6845b
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+
+contract VerifierSetAccessControllerTest is BaseTest {
+  event FeeManagerSet(address oldFeeManager, address newFeeManager);
+
+  function test_revertsIfCalledByNonOwner() public {
+    vm.expectRevert("Only callable by owner");
+    changePrank(USER);
+    s_verifier.setFeeManager(address(feeManager));
+  }
+
+  function test_successfullySetsNewFeeManager() public {
+    vm.expectEmit(true, false, false, false);
+    emit FeeManagerSet(address(0), ACCESS_CONTROLLER_ADDRESS);
+    s_verifier.setFeeManager(address(feeManager));
+    address ac = s_verifier.s_feeManager();
+    assertEq(ac, address(feeManager));
+  }
+
+  function test_setFeeManagerWhichDoesntHonourInterface() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.FeeManagerInvalid.selector));
+    s_verifier.setFeeManager(address(rewardManager));
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol
new file mode 100644
index 00000000000..dd157d2a475
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+
+contract DestinationVerifierConstructorTest is BaseTest {
+  bytes32[3] internal s_reportContext;
+
+  function test_revertsIfInitializedWithEmptyVerifierProxy() public {
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ZeroAddress.selector));
+    new DestinationVerifier(address(0));
+  }
+
+  function test_typeAndVersion() public {
+    DestinationVerifier v = new DestinationVerifier(address(s_verifierProxy));
+    assertEq(v.owner(), ADMIN);
+    string memory typeAndVersion = s_verifier.typeAndVersion();
+    assertEq(typeAndVersion, "DestinationVerifier 1.0.0");
+  }
+
+  function test_falseIfIsNotCorrectInterface() public view {
+    bool isInterface = s_verifier.supportsInterface(bytes4("abcd"));
+    assertEq(isInterface, false);
+  }
+
+  function test_trueIfIsCorrectInterface() public view {
+    bool isInterface = s_verifier.supportsInterface(DestinationVerifier.verify.selector);
+    assertEq(isInterface, true);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol
new file mode 100644
index 00000000000..574e169cf26
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract VerifierBillingTests is VerifierWithFeeManager {
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReportThree;
+
+  function setUp() public virtual override {
+    VerifierWithFeeManager.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    s_testReportThree = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+  }
+
+  function test_verifyWithLinkV3Report() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers);
+    bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+    _verify(signedReport, address(link), 0, USER);
+    assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+
+    // internal state checks
+    assertEq(feeManager.s_linkDeficit(expectedDonConfigId), 0);
+    assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigId), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_verifyWithNativeERC20() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1);
+    weights[0] = Common.AddressAndWeight(signerAddrs[0], ONE_PERCENT * 100);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    bytes memory signedReport = _generateV3EncodedBlob(
+      s_testReportThree,
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+    _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER);
+    _verify(signedReport, address(native), 0, USER);
+    assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+
+    assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+  }
+
+  function test_verifyWithNativeUnwrapped() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    bytes memory signedReport = _generateV3EncodedBlob(
+      s_testReportThree,
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+    _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE, USER);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+    assertEq(address(feeManager).balance, 0);
+  }
+
+  function test_verifyWithNativeUnwrappedReturnsChange() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    bytes memory signedReport = _generateV3EncodedBlob(
+      s_testReportThree,
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE * 2, USER);
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE);
+    assertEq(address(feeManager).balance, 0);
+  }
+}
+
+contract DestinationVerifierBulkVerifyBillingReport is VerifierWithFeeManager {
+  uint256 internal constant NUMBERS_OF_REPORTS = 5;
+
+  bytes32[3] internal s_reportContext;
+
+  function setUp() public virtual override {
+    VerifierWithFeeManager.setUp();
+    // setting a DonConfig we can reuse in the rest of tests
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+  }
+
+  function test_verifyWithBulkLink() public {
+    bytes memory signedReport = _generateV3EncodedBlob(
+      _generateV3Report(),
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS, USER);
+
+    _verifyBulk(signedReports, address(link), 0, USER);
+
+    assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS);
+    assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS);
+  }
+
+  function test_verifyWithBulkNative() public {
+    bytes memory signedReport = _generateV3EncodedBlob(
+      _generateV3Report(),
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER);
+    _verifyBulk(signedReports, address(native), 0, USER);
+    assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS);
+  }
+
+  function test_verifyWithBulkNativeUnwrapped() public {
+    bytes memory signedReport = _generateV3EncodedBlob(
+      _generateV3Report(),
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS);
+    for (uint256 i; i < NUMBERS_OF_REPORTS; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    _verifyBulk(signedReports, address(native), 200 * DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5);
+    assertEq(address(feeManager).balance, 0);
+  }
+
+  function test_verifyWithBulkNativeUnwrappedReturnsChange() public {
+    bytes memory signedReport = _generateV3EncodedBlob(
+      _generateV3Report(),
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS);
+    for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    _verifyBulk(signedReports, address(native), DEFAULT_REPORT_NATIVE_FEE * (NUMBERS_OF_REPORTS * 2), USER);
+
+    assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS);
+    assertEq(address(feeManager).balance, 0);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol
new file mode 100644
index 00000000000..8ca954b8cac
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract VerifierBillingTests is VerifierWithFeeManager {
+  uint8 MINIMAL_FAULT_TOLERANCE = 2;
+  address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1"))));
+  address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2"))));
+  address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3"))));
+  address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4"))));
+  address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5"))));
+  address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6"))));
+  address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7"))));
+
+  function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //pay the recipients
+    rewardManager.payRecipients(poolId, recipients);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReport;
+
+  function setUp() public virtual override {
+    VerifierWithFeeManager.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    s_testReport = generateReportAtTimestamp(block.timestamp);
+  }
+
+  function generateReportAtTimestamp(uint256 timestamp) public pure returns (V3Report memory) {
+    return
+      V3Report({
+        feedId: FEED_ID_V3,
+        observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+        validFromTimestamp: uint32(timestamp),
+        nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+        linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+        // ask michael about this expires at, is it usually set at what blocks
+        expiresAt: uint32(timestamp) + 500,
+        benchmarkPrice: MEDIAN,
+        bid: BID,
+        ask: ASK
+      });
+  }
+
+  function getRecipientAndWeightsGroup2() public pure returns (Common.AddressAndWeight[] memory, address[] memory) {
+    address[] memory recipients = new address[](4);
+    recipients[0] = DEFAULT_RECIPIENT_4;
+    recipients[1] = DEFAULT_RECIPIENT_5;
+    recipients[2] = DEFAULT_RECIPIENT_6;
+    recipients[3] = DEFAULT_RECIPIENT_7;
+
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](4);
+    //init each recipient with even weights. 2500 = 25% of pool
+    weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4);
+    weights[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4);
+    weights[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4);
+    weights[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4);
+    return (weights, recipients);
+  }
+
+  function getRecipientAndWeightsGroup1() public pure returns (Common.AddressAndWeight[] memory, address[] memory) {
+    address[] memory recipients = new address[](4);
+    recipients[0] = DEFAULT_RECIPIENT_1;
+    recipients[1] = DEFAULT_RECIPIENT_2;
+    recipients[2] = DEFAULT_RECIPIENT_3;
+    recipients[3] = DEFAULT_RECIPIENT_4;
+
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](4);
+    //init each recipient with even weights. 2500 = 25% of pool
+    weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4);
+    weights[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4);
+    weights[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4);
+    weights[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4);
+    return (weights, recipients);
+  }
+
+  function test_rewardsAreDistributedAccordingToWeights() public {
+    /*
+          Simple test verifying that rewards are distributed according to address and weights 
+          associated to the DonConfig used to verify the report
+         */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1);
+    weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers);
+    bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+    _verify(signedReport, address(link), 0, USER);
+    assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+
+    // internal state checks
+    assertEq(feeManager.s_linkDeficit(expectedDonConfigId), 0);
+    assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigId), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    // check the recipients are paid according to weights
+    address[] memory recipients = new address[](1);
+    recipients[0] = DEFAULT_RECIPIENT_1;
+    payRecipients(expectedDonConfigId, recipients, ADMIN);
+    assertEq(link.balanceOf(recipients[0]), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+  }
+
+  function test_rewardsAreDistributedAccordingToWeightsMultipleWeigths() public {
+    /*
+          Rewards are distributed according to AddressAndWeight's 
+          associated to the DonConfig used to verify the report:
+          - multiple recipients
+          - multiple verifications
+         */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    (Common.AddressAndWeight[] memory weights, address[] memory recipients) = getRecipientAndWeightsGroup1();
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers);
+    bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+
+    uint256 number_of_reports_verified = 10;
+
+    for (uint256 i = 0; i < number_of_reports_verified; i++) {
+      _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+      _verify(signedReport, address(link), 0, USER);
+    }
+
+    uint256 expected_pool_amount = DEFAULT_REPORT_LINK_FEE * number_of_reports_verified;
+
+    //each recipient should receive 1/4 of the pool
+    uint256 expectedRecipientAmount = expected_pool_amount / 4;
+
+    payRecipients(expectedDonConfigId, recipients, ADMIN);
+    for (uint256 i = 0; i < recipients.length; i++) {
+      // checking each recipient got rewards as set by the weights
+      assertEq(link.balanceOf(recipients[i]), expectedRecipientAmount);
+    }
+    // checking nothing left in reward manager
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+  }
+
+  function test_rewardsAreDistributedAccordingToWeightsUsingHistoricalConfigs() public {
+    /*
+          Verifies that reports verified with historical give rewards according to the verifying config AddressAndWeight.
+          - Sets two Configs: ConfigA and ConfigB, These two Configs have different Recipient and Weights 
+          - Verifies a couple reports with each config
+          - Pays recipients
+          - Asserts expected rewards for each recipient 
+         */
+
+    Signer[] memory signers = _getSigners(10);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+
+    (Common.AddressAndWeight[] memory weights, address[] memory recipients) = getRecipientAndWeightsGroup1();
+
+    // Create ConfigA
+    s_verifier.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights);
+    vm.warp(block.timestamp + 100);
+
+    V3Report memory testReportAtT1 = generateReportAtTimestamp(block.timestamp);
+    bytes memory signedReportT1 = _generateV3EncodedBlob(testReportAtT1, s_reportContext, signers);
+    bytes32 expectedDonConfigIdA = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE);
+
+    uint256 number_of_reports_verified = 2;
+
+    // advancing the blocktimestamp so we can test verifying with configs
+    vm.warp(block.timestamp + 100);
+
+    Signer[] memory signers2 = _getSigners(12);
+    address[] memory signerAddrs2 = _getSignerAddresses(signers2);
+    (Common.AddressAndWeight[] memory weights2, address[] memory recipients2) = getRecipientAndWeightsGroup2();
+
+    // Create ConfigB
+    s_verifier.setConfig(signerAddrs2, MINIMAL_FAULT_TOLERANCE, weights2);
+    bytes32 expectedDonConfigIdB = _donConfigIdFromConfigData(signerAddrs2, MINIMAL_FAULT_TOLERANCE);
+
+    V3Report memory testReportAtT2 = generateReportAtTimestamp(block.timestamp);
+
+    // verifiying using ConfigA (report with Old timestamp)
+    for (uint256 i = 0; i < number_of_reports_verified; i++) {
+      _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+      _verify(signedReportT1, address(link), 0, USER);
+    }
+
+    // verifying using ConfigB (report with new timestamp)
+    for (uint256 i = 0; i < number_of_reports_verified; i++) {
+      _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+      _verify(_generateV3EncodedBlob(testReportAtT2, s_reportContext, signers2), address(link), 0, USER);
+    }
+
+    uint256 expected_pool_amount = DEFAULT_REPORT_LINK_FEE * number_of_reports_verified;
+    assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigIdA), expected_pool_amount);
+    assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigIdB), expected_pool_amount);
+
+    // check the recipients are paid according to weights
+    payRecipients(expectedDonConfigIdA, recipients, ADMIN);
+
+    for (uint256 i = 0; i < recipients.length; i++) {
+      // //each recipient should receive 1/4 of the pool
+      assertEq(link.balanceOf(recipients[i]), expected_pool_amount / 4);
+    }
+
+    payRecipients(expectedDonConfigIdB, recipients2, ADMIN);
+
+    for (uint256 i = 1; i < recipients2.length; i++) {
+      // //each recipient should receive 1/4 of the pool
+      assertEq(link.balanceOf(recipients2[i]), expected_pool_amount / 4);
+    }
+
+    // this recipient was part of the two config weights
+    assertEq(link.balanceOf(recipients2[0]), (expected_pool_amount / 4) * 2);
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol
new file mode 100644
index 00000000000..6a90cbf3738
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {MultipleVerifierWithMultipleFeeManagers} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract MultiVerifierBillingTests is MultipleVerifierWithMultipleFeeManagers {
+  uint8 MINIMAL_FAULT_TOLERANCE = 2;
+  address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1"))));
+  address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2"))));
+  address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3"))));
+  address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4"))));
+  address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5"))));
+  address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6"))));
+  address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7"))));
+
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReport;
+
+  function setUp() public virtual override {
+    MultipleVerifierWithMultipleFeeManagers.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    s_testReport = generateReportAtTimestamp(block.timestamp);
+  }
+
+  function _verify(
+    DestinationVerifierProxy proxy,
+    bytes memory payload,
+    address feeAddress,
+    uint256 wrappedNativeValue,
+    address sender
+  ) internal {
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    proxy.verify{value: wrappedNativeValue}(payload, abi.encode(feeAddress));
+
+    changePrank(originalAddr);
+  }
+
+  function generateReportAtTimestamp(uint256 timestamp) public pure returns (V3Report memory) {
+    return
+      V3Report({
+        feedId: FEED_ID_V3,
+        observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+        validFromTimestamp: uint32(timestamp),
+        nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+        linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+        // ask michael about this expires at, is it usually set at what blocks
+        expiresAt: uint32(timestamp) + 500,
+        benchmarkPrice: MEDIAN,
+        bid: BID,
+        ask: ASK
+      });
+  }
+
+  function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public {
+    //record the current address and switch to the recipient
+    address originalAddr = msg.sender;
+    changePrank(sender);
+
+    //pay the recipients
+    rewardManager.payRecipients(poolId, recipients);
+
+    //change back to the original address
+    changePrank(originalAddr);
+  }
+
+  function test_multipleFeeManagersAndVerifiers() public {
+    /*
+       In this test we got:
+        - three verifiers (verifier, verifier2, verifier3).
+        - two fee managers (feeManager, feeManager2)
+        - one reward manager
+        
+       we glue:
+       - feeManager is used by verifier1 and verifier2
+       - feeManager is used by verifier3
+       - Rewardmanager is used by feeManager and feeManager2
+      
+      In this test we do verificatons via verifier1, verifier2 and verifier3 and check that rewards are set accordingly
+   
+    */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1);
+    weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100);
+
+    Common.AddressAndWeight[] memory weights2 = new Common.AddressAndWeight[](1);
+    weights2[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 100);
+
+    Common.AddressAndWeight[] memory weights3 = new Common.AddressAndWeight[](1);
+    weights3[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 100);
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights);
+    s_verifier2.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights2);
+    s_verifier3.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE + 1, weights3);
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers);
+    bytes32 expectedDonConfigID = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE);
+    bytes32 expectedDonConfigID2 = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE);
+    bytes32 expectedDonConfigID3 = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE + 1);
+
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+    _verify(s_verifierProxy, signedReport, address(link), 0, USER);
+    assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE);
+
+    // internal state checks
+    assertEq(feeManager.s_linkDeficit(expectedDonConfigID), 0);
+    assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigID), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE);
+
+    // check the recipients are paid according to weights
+    // These rewards happened through verifier1 and feeManager1
+    address[] memory recipients = new address[](1);
+    recipients[0] = DEFAULT_RECIPIENT_1;
+    payRecipients(expectedDonConfigID, recipients, ADMIN);
+    assertEq(link.balanceOf(recipients[0]), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+
+    // these rewards happaned through verifier2 and feeManager1
+    address[] memory recipients2 = new address[](1);
+    recipients2[0] = DEFAULT_RECIPIENT_2;
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+    _verify(s_verifierProxy2, signedReport, address(link), 0, USER);
+    payRecipients(expectedDonConfigID2, recipients2, ADMIN);
+    assertEq(link.balanceOf(recipients2[0]), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+
+    // these rewards happened through verifier3 and feeManager2
+    address[] memory recipients3 = new address[](1);
+    recipients3[0] = DEFAULT_RECIPIENT_3;
+    _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER);
+    _verify(s_verifierProxy3, signedReport, address(link), 0, USER);
+    payRecipients(expectedDonConfigID3, recipients3, ADMIN);
+    assertEq(link.balanceOf(recipients3[0]), DEFAULT_REPORT_LINK_FEE);
+    assertEq(link.balanceOf(address(rewardManager)), 0);
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol
new file mode 100644
index 00000000000..1c57295baed
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract VerifierVerifyBulkTest is BaseTest {
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReportThree;
+
+  function setUp() public virtual override {
+    BaseTest.setUp();
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+
+    s_testReportThree = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+  }
+
+  function test_revertsVerifyBulkIfNoAccess() public {
+    vm.mockCall(
+      ACCESS_CONTROLLER_ADDRESS,
+      abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER),
+      abi.encode(false)
+    );
+    bytes memory signedReport = _generateV3EncodedBlob(
+      s_testReportThree,
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    bytes[] memory signedReports = new bytes[](2);
+    signedReports[0] = signedReport;
+    signedReports[1] = signedReport;
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.AccessForbidden.selector));
+    changePrank(USER);
+    s_verifier.verifyBulk(signedReports, abi.encode(native), msg.sender);
+  }
+
+  function test_verifyBulkSingleCaseWithSingleConfig() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    // Config1
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory report = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3);
+    reportSigners[0] = signers[0];
+    reportSigners[1] = signers[1];
+    reportSigners[2] = signers[2];
+
+    bytes[] memory signedReports = new bytes[](10);
+
+    bytes memory signedReport = _generateV3EncodedBlob(report, s_reportContext, reportSigners);
+
+    for (uint256 i = 0; i < signedReports.length; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    bytes[] memory verifierResponses = s_verifierProxy.verifyBulk(signedReports, abi.encode(native));
+
+    for (uint256 i = 0; i < verifierResponses.length; i++) {
+      bytes memory verifierResponse = verifierResponses[i];
+      assertReportsEqual(verifierResponse, report);
+    }
+  }
+
+  function test_verifyBulkWithSingleConfigOneVerifyFails() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    // Config1
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3);
+    reportSigners[0] = signers[0];
+    reportSigners[1] = signers[1];
+    reportSigners[2] = signers[2];
+
+    bytes[] memory signedReports = new bytes[](11);
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners);
+
+    for (uint256 i = 0; i < 10; i++) {
+      signedReports[i] = signedReport;
+    }
+
+    // Making the last report in  this batch not verifiable
+    BaseTest.Signer[] memory reportSigners2 = new BaseTest.Signer[](3);
+    reportSigners2[0] = signers[30];
+    reportSigners2[1] = signers[29];
+    reportSigners2[2] = signers[28];
+    signedReports[10] = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners2);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verifyBulk(signedReports, abi.encode(native));
+  }
+}
diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol
new file mode 100644
index 00000000000..658bf4f127c
--- /dev/null
+++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol
@@ -0,0 +1,711 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.19;
+
+import {BaseTest} from "./BaseDestinationVerifierTest.t.sol";
+import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol";
+import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol";
+import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol";
+import {Common} from "../../../libraries/Common.sol";
+
+contract VerifierVerifyTest is BaseTest {
+  bytes32[3] internal s_reportContext;
+  V3Report internal s_testReportThree;
+
+  function setUp() public virtual override {
+    BaseTest.setUp();
+
+    s_testReportThree = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+  }
+
+  function test_verifyReport() public {
+    // Simple use case just setting a config and verifying a report
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers);
+
+    bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native));
+    assertReportsEqual(verifierResponse, s_testReportThree);
+  }
+
+  function test_verifyTooglingActiveFlagsDonConfigs() public {
+    // sets config
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+    // verifies report
+    bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native));
+    assertReportsEqual(verifierResponse, s_testReportThree);
+
+    // test verifying via a config that is deactivated
+    s_verifier.setConfigActive(0, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    // test verifying via a reactivated config
+    s_verifier.setConfigActive(0, true);
+    verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native));
+    assertReportsEqual(verifierResponse, s_testReportThree);
+  }
+
+  function test_failToVerifyReportIfNotEnoughSigners() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    // only one signer, signers < MINIMAL_FAULT_TOLERANCE
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](1);
+    signersSubset2[0] = signers[4];
+
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_failToVerifyReportIfNoSigners() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    // No signers for this report
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](0);
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.NoSigners.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_failToVerifyReportIfDupSigners() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+    // One signer is repeated
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](4);
+    signersSubset2[0] = signers[0];
+    signersSubset2[1] = signers[1];
+    // repeated signers
+    signersSubset2[2] = signers[2];
+    signersSubset2[3] = signers[2];
+
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_failToVerifyReportIfSignerNotInConfig() public {
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    // one report whose signer is not in the config
+    BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](4);
+    // these signers are part ofm the config
+    reportSigners[0] = signers[4];
+    reportSigners[1] = signers[5];
+    reportSigners[2] = signers[6];
+    // this single signer is not in the config
+    reportSigners[3] = signers[7];
+
+    bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_canVerifyOlderV3ReportsWithOlderConfigs() public {
+    /*
+          This test is checking we can use historical Configs to verify reports:
+          - DonConfigA has signers {A, B, C, E} is set at time T1
+          - DonConfigB has signers {A, B, C, D} is set at time T2
+          - checks we can verify a report with {B, C, D} signers (via DonConfigB)
+          - checks we can verify a report with {B, C, E} signers and timestamp below T2 (via DonConfigA historical config)
+          - checks we can't verify a report with {B, C, E} signers and timestamp above T2 (it gets verivied via DonConfigB)
+          - sets DonConfigA as deactivated
+          - checks we can't verify a report with {B, C, E} signers and timestamp below T2 (via DonConfigA)
+         */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    // Config1
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7);
+    signersSubset2[0] = signers[0];
+    signersSubset2[1] = signers[1];
+    signersSubset2[2] = signers[2];
+    signersSubset2[3] = signers[3];
+    signersSubset2[4] = signers[4];
+    signersSubset2[5] = signers[5];
+    signersSubset2[6] = signers[29];
+    address[] memory signersAddrSubset2 = _getSignerAddresses(signersSubset2);
+
+    V3Report memory reportAtSetConfig1Timestmap = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    vm.warp(block.timestamp + 100);
+
+    // Config2
+    s_verifier.setConfig(signersAddrSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory reportAtSetConfig2Timestmap = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](5);
+    reportSigners[0] = signers[0];
+    reportSigners[1] = signers[1];
+    reportSigners[2] = signers[2];
+    reportSigners[3] = signers[3];
+    reportSigners[4] = signers[29];
+
+    bytes memory signedReport = _generateV3EncodedBlob(reportAtSetConfig2Timestmap, s_reportContext, reportSigners);
+
+    // this report is verified via Config2
+    bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native));
+    assertReportsEqual(verifierResponse, reportAtSetConfig2Timestmap);
+
+    BaseTest.Signer[] memory reportSigners2 = new BaseTest.Signer[](5);
+    reportSigners2[0] = signers[0];
+    reportSigners2[1] = signers[1];
+    reportSigners2[2] = signers[2];
+    reportSigners2[3] = signers[3];
+    reportSigners2[4] = signers[6];
+
+    bytes memory signedReport2 = _generateV3EncodedBlob(reportAtSetConfig1Timestmap, s_reportContext, reportSigners2);
+
+    // this report is verified via Config1 (using a historical config)
+    bytes memory verifierResponse2 = s_verifierProxy.verify(signedReport2, abi.encode(native));
+    assertReportsEqual(verifierResponse2, reportAtSetConfig1Timestmap);
+
+    // same report with same signers but with a higher timestamp gets verified via Config2
+    // which means verification fails
+    bytes memory signedReport3 = _generateV3EncodedBlob(reportAtSetConfig2Timestmap, s_reportContext, reportSigners2);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport3, abi.encode(native));
+
+    // deactivating Config1 and trying a reverifications ends in failure
+    s_verifier.setConfigActive(0, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport2, abi.encode(native));
+  }
+
+  function test_revertsVerifyIfNoAccess() public {
+    vm.mockCall(
+      ACCESS_CONTROLLER_ADDRESS,
+      abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER),
+      abi.encode(false)
+    );
+    bytes memory signedReport = _generateV3EncodedBlob(
+      s_testReportThree,
+      s_reportContext,
+      _getSigners(FAULT_TOLERANCE + 1)
+    );
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.AccessForbidden.selector));
+
+    changePrank(USER);
+    s_verifier.verify(signedReport, abi.encode(native), msg.sender);
+  }
+
+  function test_canVerifyNewerReportsWithNewerConfigs() public {
+    /*
+          This test is checking that we use prefer verifiying via newer configs instead of old ones.
+          - DonConfigA has signers {A, B, C, E} is set at time T1
+          - DonConfigB has signers {F, G, H, I} is set at time T2
+          - DonConfigC has signers {J, K, L, M } is set at time T3
+          - checks we can verify a report with {K, L, M} signers (via DonConfigC)
+         */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    // Config1
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7);
+    signersSubset2[0] = signers[7];
+    signersSubset2[1] = signers[8];
+    signersSubset2[2] = signers[9];
+    signersSubset2[3] = signers[10];
+    signersSubset2[4] = signers[11];
+    signersSubset2[5] = signers[12];
+    signersSubset2[6] = signers[13];
+
+    address[] memory signersAddrSubset2 = _getSignerAddresses(signersSubset2);
+    // Config2
+    s_verifier.setConfig(signersAddrSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    BaseTest.Signer[] memory signersSubset3 = new BaseTest.Signer[](7);
+    signersSubset3[0] = signers[30];
+    signersSubset3[1] = signers[29];
+    signersSubset3[2] = signers[28];
+    signersSubset3[3] = signers[27];
+    signersSubset3[4] = signers[26];
+    signersSubset3[5] = signers[25];
+    signersSubset3[6] = signers[24];
+
+    address[] memory signersAddrSubset3 = _getSignerAddresses(signersSubset3);
+    // Config3
+    s_verifier.setConfig(signersAddrSubset3, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory report = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3);
+    reportSigners[0] = signers[30];
+    reportSigners[1] = signers[29];
+    reportSigners[2] = signers[28];
+
+    bytes memory signedReport = _generateV3EncodedBlob(report, s_reportContext, reportSigners);
+
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_rollingOutConfiguration() public {
+    /*
+          This test is checking that we can roll out to a new DON without downtime using a transition configuration
+          - DonConfigA has signers {A, B, C} is set at time T1
+          - DonConfigB (transition config) has signers {A, B, C, D, E, F} is set at time T2
+          - DonConfigC has signers {D, E, F} is set at time T3
+          
+          - checks we can verify a report with {A, B, C} signers (via DonConfigA) at time between T1 and T2
+          - checks we can verify a report with {A, B, C} signers (via DonConfigB) at time between T2 and T3
+          - checks we can verify a report with {D, E, F} signers (via DonConfigB) at time between T2 and T3
+          - checks we can verify a report with {D, E, F} signers (via DonConfigC) at time > T3
+          - checks we can't verify a report with {A, B, C} signers (via DonConfigC) and timestamp >T3 at time > T3
+          - checks we can verify a report with {A, B, C} signers (via DonConfigC) and timestamp between T2 and T3  at time > T3 (historical check)
+
+         */
+
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7);
+    signersSubset1[0] = signers[0];
+    signersSubset1[1] = signers[1];
+    signersSubset1[2] = signers[2];
+    signersSubset1[3] = signers[3];
+    signersSubset1[4] = signers[4];
+    signersSubset1[5] = signers[5];
+    signersSubset1[6] = signers[6];
+
+    // ConfigA
+    address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1);
+    s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory reportT1 = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSignersConfigA = new BaseTest.Signer[](3);
+    reportSignersConfigA[0] = signers[0];
+    reportSignersConfigA[1] = signers[1];
+    reportSignersConfigA[2] = signers[2];
+
+    // just testing ConfigA
+    bytes memory signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    vm.warp(block.timestamp + 100);
+
+    BaseTest.Signer[] memory signersSuperset = new BaseTest.Signer[](14);
+    // signers in ConfigA
+    signersSuperset[0] = signers[0];
+    signersSuperset[1] = signers[1];
+    signersSuperset[2] = signers[2];
+    signersSuperset[3] = signers[3];
+    signersSuperset[4] = signers[4];
+    signersSuperset[5] = signers[5];
+    signersSuperset[6] = signers[6];
+    // new signers
+    signersSuperset[7] = signers[7];
+    signersSuperset[8] = signers[8];
+    signersSuperset[9] = signers[9];
+    signersSuperset[10] = signers[10];
+    signersSuperset[11] = signers[11];
+    signersSuperset[12] = signers[12];
+    signersSuperset[13] = signers[13];
+
+    BaseTest.Signer[] memory reportSignersConfigC = new BaseTest.Signer[](3);
+    reportSignersConfigC[0] = signers[7];
+    reportSignersConfigC[1] = signers[8];
+    reportSignersConfigC[2] = signers[9];
+
+    // ConfigB (transition Config)
+    address[] memory signersAddrsSuperset = _getSignerAddresses(signersSuperset);
+    s_verifier.setConfig(signersAddrsSuperset, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory reportT2 = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    // testing we can verify a fresh (block timestamp) report with ConfigA signers. This should use ConfigB
+    signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigA);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    // testing we can verify an old ( non fresh block timestamp) report with ConfigA signers. This should use ConfigA
+    signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    // deactivating to make sure we are really verifiying via ConfigA
+    s_verifier.setConfigActive(0, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    s_verifier.setConfigActive(0, true);
+
+    // testing we can verify a fresh  (block timestamp) report with the new signers.  This should use ConfigB
+    signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigC);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    vm.warp(block.timestamp + 100);
+
+    // Adding ConfigC
+    BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7);
+    signersSubset2[0] = signers[7];
+    signersSubset2[1] = signers[8];
+    signersSubset2[2] = signers[9];
+    signersSubset2[3] = signers[10];
+    signersSubset2[4] = signers[11];
+    signersSubset2[5] = signers[12];
+    signersSubset2[6] = signers[13];
+    address[] memory signersAddrsSubset2 = _getSignerAddresses(signersSubset2);
+    s_verifier.setConfig(signersAddrsSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    V3Report memory reportT3 = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    // testing we can verify reports with ConfigC signers
+    signedReport = _generateV3EncodedBlob(reportT3, s_reportContext, reportSignersConfigC);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    //  testing an old report (block timestamp) with ConfigC signers should  verify via ConfigB
+    signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigC);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    // deactivating to make sure we are really verifiying via ConfigB
+    s_verifier.setConfigActive(1, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    s_verifier.setConfigActive(1, true);
+
+    // testing a recent report with ConfigA signers should not verify
+    signedReport = _generateV3EncodedBlob(reportT3, s_reportContext, reportSignersConfigA);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+
+    // testing an old report (block timestamp) with ConfigA signers should  verify via ConfigB
+    signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigA);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    // deactivating to make sure we are really verifiying via ConfigB
+    s_verifier.setConfigActive(1, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    s_verifier.setConfigActive(1, true);
+
+    // testing an old report (block timestamp) with ConfigA signers should  verify via ConfigA
+    signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA);
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    // deactivating to make sure we are really verifiying via ConfigB
+    s_verifier.setConfigActive(0, false);
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+    s_verifier.setConfigActive(0, true);
+  }
+
+  function test_verifyFailsWhenReportIsOlderThanConfig() public {
+    /*
+          - SetConfig A at time T0
+          - SetConfig B at time T1
+          - tries verifing report issued at blocktimestmap < T0
+          
+          this test is failing: ToDo Ask Michael
+         */
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+    address[] memory signerAddrs = _getSignerAddresses(signers);
+    s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1)));
+
+    vm.warp(block.timestamp + 100);
+
+    V3Report memory reportAtTMinus100 = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp - 100),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+    vm.warp(block.timestamp + 100);
+    s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0));
+
+    bytes memory signedReport = _generateV3EncodedBlob(reportAtTMinus100, s_reportContext, signers);
+
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedReport, abi.encode(native));
+  }
+
+  function test_scenarioRollingNewChainWithHistoricConfigs() public {
+    /*
+       This test is checking that we can roll out in a new network and set historic configurations :
+       - Stars with a chain at blocktimestamp 1000
+       - SetConfigA with teimstamp 100 
+       - SetConfigB with timesmtap 200
+       - SetConfigC with timestamp current 
+       - tries verifying reports for all the configs
+    */
+
+    vm.warp(block.timestamp + 1000);
+
+    Signer[] memory signers = _getSigners(MAX_ORACLES);
+
+    uint8 MINIMAL_FAULT_TOLERANCE = 2;
+    BaseTest.Signer[] memory signersA = new BaseTest.Signer[](7);
+    signersA[0] = signers[0];
+    signersA[1] = signers[1];
+    signersA[2] = signers[2];
+    signersA[3] = signers[3];
+    signersA[4] = signers[4];
+    signersA[5] = signers[5];
+    signersA[6] = signers[6];
+
+    // ConfigA (historical config)
+    uint32 configATimestmap = 100;
+    address[] memory signersAddrA = _getSignerAddresses(signersA);
+    s_verifier.setConfigWithActivationTime(
+      signersAddrA,
+      MINIMAL_FAULT_TOLERANCE,
+      new Common.AddressAndWeight[](0),
+      configATimestmap
+    );
+
+    // ConfigB (historical config)
+    uint32 configBTimestmap = 200;
+    // Config B
+    BaseTest.Signer[] memory signersB = new BaseTest.Signer[](7);
+    // signers in ConfigA
+    signersB[0] = signers[8];
+    signersB[1] = signers[9];
+    signersB[2] = signers[10];
+    signersB[3] = signers[11];
+    signersB[4] = signers[12];
+    signersB[5] = signers[13];
+    signersB[6] = signers[14];
+    address[] memory signersAddrsB = _getSignerAddresses(signersB);
+    s_verifier.setConfigWithActivationTime(
+      signersAddrsB,
+      MINIMAL_FAULT_TOLERANCE,
+      new Common.AddressAndWeight[](0),
+      configBTimestmap
+    );
+
+    // ConfigC (config at current timestamp)
+    //    BaseTest.Signer[] memory signersC = new BaseTest.Signer[](7);
+    // signers in ConfigA
+    signersB[6] = signers[15];
+    address[] memory signersAddrsC = _getSignerAddresses(signersB);
+    s_verifier.setConfig(signersAddrsC, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0));
+
+    vm.warp(block.timestamp + 10);
+
+    // historical report
+    V3Report memory s_testReportA = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(101),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp + 1000),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    // historical report
+    V3Report memory s_testReportB = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(201),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp + 1000),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    // report at recent timestamp
+    V3Report memory s_testReportC = V3Report({
+      feedId: FEED_ID_V3,
+      observationsTimestamp: OBSERVATIONS_TIMESTAMP,
+      validFromTimestamp: uint32(block.timestamp),
+      nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE),
+      linkFee: uint192(DEFAULT_REPORT_LINK_FEE),
+      expiresAt: uint32(block.timestamp + 1000),
+      benchmarkPrice: MEDIAN,
+      bid: BID,
+      ask: ASK
+    });
+
+    BaseTest.Signer[] memory reportSignersA = new BaseTest.Signer[](3);
+    reportSignersA[0] = signers[0];
+    reportSignersA[1] = signers[1];
+    reportSignersA[2] = signers[2];
+
+    BaseTest.Signer[] memory reportSignersB = new BaseTest.Signer[](3);
+    reportSignersB[0] = signers[8];
+    reportSignersB[1] = signers[9];
+    reportSignersB[2] = signers[14];
+
+    BaseTest.Signer[] memory reportSignersC = new BaseTest.Signer[](3);
+    reportSignersC[0] = signers[15];
+    reportSignersC[1] = signers[13];
+    reportSignersC[2] = signers[12];
+
+    bytes memory signedReportA = _generateV3EncodedBlob(s_testReportA, s_reportContext, reportSignersA);
+    bytes memory signedReportB = _generateV3EncodedBlob(s_testReportB, s_reportContext, reportSignersB);
+    bytes memory signedReportC = _generateV3EncodedBlob(s_testReportC, s_reportContext, reportSignersC);
+
+    // verifying historical reports
+    s_verifierProxy.verify(signedReportA, abi.encode(native));
+    s_verifierProxy.verify(signedReportB, abi.encode(native));
+    // verifiying a current report
+    s_verifierProxy.verify(signedReportC, abi.encode(native));
+
+    // current report verified by historical report fails
+    bytes memory signedNewReportWithOldSignatures = _generateV3EncodedBlob(
+      s_testReportC,
+      s_reportContext,
+      reportSignersA
+    );
+    vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector));
+    s_verifierProxy.verify(signedNewReportWithOldSignatures, abi.encode(native));
+  }
+}
diff --git a/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go b/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go
new file mode 100644
index 00000000000..b87cf068ac5
--- /dev/null
+++ b/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go
@@ -0,0 +1,1790 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package destination_fee_manager
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"strings"
+
+	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated"
+)
+
+var (
+	_ = errors.New
+	_ = big.NewInt
+	_ = strings.NewReader
+	_ = ethereum.NotFound
+	_ = bind.Bind
+	_ = common.Big1
+	_ = types.BloomLookup
+	_ = event.NewSubscription
+	_ = abi.ConvertType
+)
+
+type CommonAddressAndWeight struct {
+	Addr   common.Address
+	Weight uint64
+}
+
+type CommonAsset struct {
+	AssetAddress common.Address
+	Amount       *big.Int
+}
+
+type IDestinationRewardManagerFeePayment struct {
+	PoolId [32]byte
+	Amount *big.Int
+}
+
+var DestinationFeeManagerMetaData = &bind.MetaData{
+	ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_linkAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_nativeAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_verifierAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_rewardManagerAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ExpiredReport\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDeposit\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDiscount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidQuote\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReceivingAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSurcharge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PoolIdMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroDeficit\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"structCommon.Asset\",\"name\":\"fee\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"structCommon.Asset\",\"name\":\"reward\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"appliedDiscount\",\"type\":\"uint256\"}],\"name\":\"DiscountApplied\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"indexed\":false,\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"rewards\",\"type\":\"tuple[]\"}],\"name\":\"InsufficientLink\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"linkQuantity\",\"type\":\"uint256\"}],\"name\":\"LinkDeficitCleared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"newSurcharge\",\"type\":\"uint64\"}],\"name\":\"NativeSurchargeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"discount\",\"type\":\"uint64\"}],\"name\":\"SubscriberDiscountUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"adminAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"addVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"quoteAddress\",\"type\":\"address\"}],\"name\":\"getFeeAndReward\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structCommon.Asset\",\"name\":\"\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structCommon.Asset\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_linkAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_nativeAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_rewardManager\",\"outputs\":[{\"internalType\":\"contractIDestinationRewardManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"linkAvailableForPayment\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"payLinkDeficit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"}],\"name\":\"processFee\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"poolIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"payloads\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"}],\"name\":\"processFeeBulk\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"removeVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_linkDeficit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_nativeSurcharge\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_subscriberDiscounts\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_verifierAddressList\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"rewardRecipientAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"surcharge\",\"type\":\"uint64\"}],\"name\":\"setNativeSurcharge\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"rewardManagerAddress\",\"type\":\"address\"}],\"name\":\"setRewardManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"discount\",\"type\":\"uint64\"}],\"name\":\"updateSubscriberDiscount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
+	Bin: "0x60c06040523480156200001157600080fd5b5060405162003c3238038062003c328339810160408190526200003491620002af565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620001e7565b5050506001600160a01b0384161580620000df57506001600160a01b038316155b80620000f257506001600160a01b038216155b806200010557506001600160a01b038116155b15620001245760405163e6c4247b60e01b815260040160405180910390fd5b6001600160a01b03848116608081905284821660a05283821660008181526004602081905260409182902080546001600160a01b03199081169094179055600580549093169486169485179092555163095ea7b360e01b81529081019290925260001960248301529063095ea7b3906044016020604051808303816000875af1158015620001b6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001dc91906200030c565b505050505062000337565b336001600160a01b03821603620002415760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b0381168114620002aa57600080fd5b919050565b60008060008060808587031215620002c657600080fd5b620002d18562000292565b9350620002e16020860162000292565b9250620002f16040860162000292565b9150620003016060860162000292565b905092959194509250565b6000602082840312156200031f57600080fd5b815180151581146200033057600080fd5b9392505050565b60805160a051613850620003e26000396000818161033b0152818161151c0152818161177a015281816117d101528181611a7e0152818161248e01526125370152600081816105430152818161098e01528181610a9801528181610e9b015281816111f4015281816114c50152818161165c0152818161179f015281816118280152818161196d015281816119da01528181611a1a01528181612109015261262b01526138506000f3fe60806040526004361061018b5760003560e01c806386968cfd116100d6578063d09dc3391161007f578063ea4b861b11610059578063ea4b861b14610531578063f2fde38b14610565578063f65df9621461058557600080fd5b8063d09dc33914610491578063e03dab1a146104a6578063e389d9a41461051157600080fd5b80639000b3d6116100b05780639000b3d614610431578063ca2dfd0a14610451578063ce7817d11461047157600080fd5b806386968cfd146103b557806387d6d843146103c85780638da5cb5b1461040657600080fd5b80633690750911610138578063638786681161011257806363878668146103295780637700feeb1461035d57806379ba5097146103a057600080fd5b806336907509146102a45780633aa5ac07146102b7578063505380941461030957600080fd5b8063181f5a7711610169578063181f5a77146102225780631d4d84a21461026e57806332f5f7461461028e57600080fd5b8063013f542b1461019057806301ffc9a7146101d0578063153ee55414610200575b600080fd5b34801561019c57600080fd5b506101bd6101ab366004612d9a565b60036020526000908152604090205481565b6040519081526020015b60405180910390f35b3480156101dc57600080fd5b506101f06101eb366004612db3565b6105a5565b60405190151581526020016101c7565b34801561020c57600080fd5b5061022061021b366004612e27565b6108ea565b005b34801561022e57600080fd5b50604080518082018252601b81527f44657374696e6174696f6e4665654d616e6167657220312e302e300000000000602082015290516101c79190612e68565b34801561027a57600080fd5b50610220610289366004612edf565b610b0b565b34801561029a57600080fd5b506101bd60065481565b6102206102b2366004613036565b610c9f565b3480156102c357600080fd5b506005546102e49073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101c7565b34801561031557600080fd5b50610220610324366004613156565b610f50565b34801561033557600080fd5b506102e47f000000000000000000000000000000000000000000000000000000000000000081565b34801561036957600080fd5b506102e4610378366004612e27565b60046020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b3480156103ac57600080fd5b50610220610fea565b6102206103c3366004613171565b6110ec565b3480156103d457600080fd5b506101bd6103e33660046131fd565b600260209081526000938452604080852082529284528284209052825290205481565b34801561041257600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166102e4565b34801561043d57600080fd5b5061022061044c366004612e27565b61126d565b34801561045d57600080fd5b5061022061046c366004612e27565b611370565b34801561047d57600080fd5b5061022061048c366004613234565b61146f565b34801561049d57600080fd5b506101bd61162b565b3480156104b257600080fd5b506104c66104c1366004613313565b6116e1565b60408051845173ffffffffffffffffffffffffffffffffffffffff9081168252602095860151868301528451169181019190915292909101516060830152608082015260a0016101c7565b34801561051d57600080fd5b5061022061052c366004612d9a565b611ae0565b34801561053d57600080fd5b506102e47f000000000000000000000000000000000000000000000000000000000000000081565b34801561057157600080fd5b50610220610580366004612e27565b611c95565b34801561059157600080fd5b506102206105a036600461336c565b611ca9565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167fe03dab1a00000000000000000000000000000000000000000000000000000000148061063857507fffffffff0000000000000000000000000000000000000000000000000000000082167f5053809400000000000000000000000000000000000000000000000000000000145b8061068457507fffffffff0000000000000000000000000000000000000000000000000000000082167fce7817d100000000000000000000000000000000000000000000000000000000145b806106d057507fffffffff0000000000000000000000000000000000000000000000000000000082167f1d4d84a200000000000000000000000000000000000000000000000000000000145b8061071c57507fffffffff0000000000000000000000000000000000000000000000000000000082167fd09dc33900000000000000000000000000000000000000000000000000000000145b8061076857507fffffffff0000000000000000000000000000000000000000000000000000000082167fe389d9a400000000000000000000000000000000000000000000000000000000145b806107b457507fffffffff0000000000000000000000000000000000000000000000000000000082167f9000b3d600000000000000000000000000000000000000000000000000000000145b8061080057507fffffffff0000000000000000000000000000000000000000000000000000000082167fca2dfd0a00000000000000000000000000000000000000000000000000000000145b8061084c57507fffffffff0000000000000000000000000000000000000000000000000000000082167f86968cfd00000000000000000000000000000000000000000000000000000000145b8061089857507fffffffff0000000000000000000000000000000000000000000000000000000082167f3690750900000000000000000000000000000000000000000000000000000000145b806108e457507fffffffff0000000000000000000000000000000000000000000000000000000082167ff65df96200000000000000000000000000000000000000000000000000000000145b92915050565b6108f2611dbd565b73ffffffffffffffffffffffffffffffffffffffff811661093f576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152600060248201527f00000000000000000000000000000000000000000000000000000000000000009091169063095ea7b3906044016020604051808303816000875af11580156109d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fd91906133eb565b50600580547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8381169182179092556040517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201527f00000000000000000000000000000000000000000000000000000000000000009091169063095ea7b3906044016020604051808303816000875af1158015610ae3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b0791906133eb565b5050565b610b13611dbd565b73ffffffffffffffffffffffffffffffffffffffff8316610be85760008273ffffffffffffffffffffffffffffffffffffffff168277ffffffffffffffffffffffffffffffffffffffffffffffff1660405160006040518083038185875af1925050503d8060008114610ba2576040519150601f19603f3d011682016040523d82523d6000602084013e610ba7565b606091505b5050905080610be2576040517fef2af20100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b610c2373ffffffffffffffffffffffffffffffffffffffff84168377ffffffffffffffffffffffffffffffffffffffffffffffff8416611e40565b6040805133815273ffffffffffffffffffffffffffffffffffffffff848116602083015285168183015277ffffffffffffffffffffffffffffffffffffffffffffffff8316606082015290517f7ff78a71698bdb18dcca96f52ab25e0a1b146fb6a49adf8e6845299e49021f299181900360800190a15b505050565b3360008181526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1614610cfc576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85518414610d35576040517e154a0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008467ffffffffffffffff811115610d5057610d50612f2a565b604051908082528060200260200182016040528015610d8957816020015b610d76612d0d565b815260200190600190039081610d6e5790505b5090506000806000805b88811015610f16576000801b8b8281518110610db157610db161340d565b602002602001015103610df0576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000610e248d8d86818110610e0a57610e0a61340d565b9050602002810190610e1c919061343c565b8d8d8d611f14565b9250925092508260200151600014610f025760405180608001604052808f8681518110610e5357610e5361340d565b6020026020010151815260200184815260200183815260200182815250888680610e7c906134d0565b975081518110610e8e57610e8e61340d565b60200260200101819052507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16836000015173ffffffffffffffffffffffffffffffffffffffff1603610efb57866001019650610f02565b8560010195505b50505080610f0f906134d0565b9050610d93565b5082151580610f2457508115155b15610f3a57610f3585858585612024565b610f44565b610f44853461281e565b50505050505050505050565b610f58611dbd565b670de0b6b3a764000067ffffffffffffffff82161115610fa4576040517f05e8ac2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660068190556040519081527f08f7c0d17932ddb8523bc06754d42ff19ebc77d76a8b9bfde02c28ab1ed3d6399060200160405180910390a150565b60015473ffffffffffffffffffffffffffffffffffffffff163314611070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3360008181526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1614611149576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080600061115b8888888888611f14565b925092509250826020015160000361117f57611177843461281e565b505050611265565b604080516001808252818301909252600091816020015b61119e612d0d565b81526020019060019003908161119657905050905060405180608001604052808b815260200185815260200184815260200183815250816000815181106111e7576111e761340d565b60200260200101819052507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846000015173ffffffffffffffffffffffffffffffffffffffff160361125757610f35858260016000612024565b610f44858260006001612024565b505050505050565b611275611dbd565b73ffffffffffffffffffffffffffffffffffffffff81166112c2576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8181166000908152600460205260409020541615611321576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600081815260046020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169091179055565b611378611dbd565b73ffffffffffffffffffffffffffffffffffffffff81166113c5576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81811660009081526004602052604090205416611423576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600090815260046020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b611477611dbd565b670de0b6b3a764000067ffffffffffffffff821611156114c3576040517f997ea36000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561156b57507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b156115a2576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff848116600081815260026020908152604080832088845282528083209487168084529482529182902067ffffffffffffffff86169081905582519485529084015285927f5eba5a8afa39780f0f99b6cbeb95f3da6a7040ca00abd46bdc91a0a060134139910160405180910390a350505050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa1580156116b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116dc9190613508565b905090565b604080518082018252600080825260208083018290528351808501855282815280820183905284518086018652838152808301849052855180870190965283865291850183905292938261173488613521565b90507fffff0000000000000000000000000000000000000000000000000000000000008082169081016117cf57505073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811683527f0000000000000000000000000000000000000000000000000000000000000000168152909350915060009050611ad7565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161415801561187757507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614155b156118ae576040517ff861803000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008b8060200190518101906118c7919061357a565b77ffffffffffffffffffffffffffffffffffffffffffffffff91821698509116955063ffffffff169350505042821015905061192f576040517fb6c405f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff808e16600090815260026020908152604080832089845282528083208f851684529091529020547f000000000000000000000000000000000000000000000000000000000000000090911687526119be6119a682670de0b6b3a76400006135e0565b6119b090866135f3565b670de0b6b3a7640000612867565b602088015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000008116908d1603611a4b5773ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016885260208088015190890152611ac8565b600654600090611a67906119a690670de0b6b3a764000061360a565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168a529050611ac1611ab783670de0b6b3a76400006135e0565b6119b090836135f3565b60208a0152505b96995094975094955050505050505b93509350939050565b611ae8611dbd565b60008181526003602052604081205490819003611b31576040517f03aad31200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000828152600360205260408082208290558051600180825281830190925290816020015b6040805180820190915260008082526020820152815260200190600190039081611b5657905050905060405180604001604052808481526020018377ffffffffffffffffffffffffffffffffffffffffffffffff1681525081600081518110611bc157611bc161340d565b60209081029190910101526005546040517fb0d9fa1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063b0d9fa1990611c24908490309060040161367d565b600060405180830381600087803b158015611c3e57600080fd5b505af1158015611c52573d6000803e3d6000fd5b50505050827f843f0b103e50b42b08f9d30f12f961845a6d02623730872e24644899c0dd989583604051611c8891815260200190565b60405180910390a2505050565b611c9d611dbd565b611ca68161289f565b50565b3360008181526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1614801590611cf5575060005473ffffffffffffffffffffffffffffffffffffffff163314155b15611d2c576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546040517f14060f2300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116906314060f2390611d86908690869086906004016136b5565b600060405180830381600087803b158015611da057600080fd5b505af1158015611db4573d6000803e3d6000fd5b50505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611e3e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401611067565b565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610c9a9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612994565b6040805180820190915260008082526020820152604080518082019091526000808252602082015260003073ffffffffffffffffffffffffffffffffffffffff851603611f8d576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611f9b888a018a613735565b915050600081611faa90613521565b905060007e010000000000000000000000000000000000000000000000000000000000007fffff00000000000000000000000000000000000000000000000000000000000083161461200557612002888a018a612e27565b90505b6120108784836116e1565b955095509550505050955095509592505050565b60008267ffffffffffffffff81111561203f5761203f612f2a565b60405190808252806020026020018201604052801561208457816020015b604080518082019091526000808252602082015281526020019060019003908161205d5790505b50905060008267ffffffffffffffff8111156120a2576120a2612f2a565b6040519080825280602002602001820160405280156120e757816020015b60408051808201909152600080825260208201528152602001906001900390816120c05790505b5090506000808080806120fa888a61360a565b905060005b81811015612449577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168b82815181106121505761215061340d565b6020026020010151602001516000015173ffffffffffffffffffffffffffffffffffffffff16036122165760405180604001604052808c83815181106121985761219861340d565b60200260200101516000015181526020018c83815181106121bb576121bb61340d565b6020026020010151604001516020015177ffffffffffffffffffffffffffffffffffffffffffffffff168152508885806121f4906134d0565b9650815181106122065761220661340d565b602002602001018190525061230b565b60405180604001604052808c83815181106122335761223361340d565b60200260200101516000015181526020018c83815181106122565761225661340d565b6020026020010151604001516020015177ffffffffffffffffffffffffffffffffffffffffffffffff1681525087848061228f906134d0565b9550815181106122a1576122a161340d565b60200260200101819052508a81815181106122be576122be61340d565b60200260200101516020015160200151866122d9919061360a565b95508a81815181106122ed576122ed61340d565b6020026020010151604001516020015185612308919061360a565b94505b8a818151811061231d5761231d61340d565b602002602001015160600151600014612439578b73ffffffffffffffffffffffffffffffffffffffff168b82815181106123595761235961340d565b6020026020010151600001517f88b15eb682210089cddf967648e2cb2a4535aeadc8f8f36050922e33c04e71258d84815181106123985761239861340d565b6020026020010151602001518e85815181106123b6576123b661340d565b6020026020010151604001518f86815181106123d4576123d461340d565b60200260200101516060015160405161243093929190835173ffffffffffffffffffffffffffffffffffffffff908116825260209485015185830152835116604082015291909201516060820152608081019190915260a00190565b60405180910390a35b612442816134d0565b90506120ff565b5060003415612517573486111561248c576040517fb2e532de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0876040518263ffffffff1660e01b81526004016000604051808303818588803b1580156124f457600080fd5b505af1158015612508573d6000803e3d6000fd5b5050505050853403905061255f565b851561255f5761255f73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168d3089612aa0565b8751156125f657600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b0d9fa19898e6040518363ffffffff1660e01b81526004016125c392919061367d565b600060405180830381600087803b1580156125dd57600080fd5b505af11580156125f1573d6000803e3d6000fd5b505050505b865115612806576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015612687573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126ab9190613508565b85111561277b5760005b875181101561273e578781815181106126d0576126d061340d565b60200260200101516020015177ffffffffffffffffffffffffffffffffffffffffffffffff16600360008a848151811061270c5761270c61340d565b60209081029190910181015151825281019190915260400160002080549091019055612737816134d0565b90506126b5565b507ff52e5907b69d97c33392936c12d78b494463b78c5b72df50b4c497eee5720b678760405161276e91906137d9565b60405180910390a1612806565b6005546040517fb0d9fa1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063b0d9fa19906127d3908a90309060040161367d565b600060405180830381600087803b1580156127ed57600080fd5b505af1158015612801573d6000803e3d6000fd5b505050505b6128108c8261281e565b505050505050505050505050565b8015610b075760405173ffffffffffffffffffffffffffffffffffffffff83169082156108fc029083906000818181858888f19350505050158015610c9a573d6000803e3d6000fd5b60008215612895578161287b6001856135e0565b61288591906137ec565b61289090600161360a565b612898565b60005b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff82160361291e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401611067565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006129f6826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16612afe9092919063ffffffff16565b805190915015610c9a5780806020019051810190612a1491906133eb565b610c9a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401611067565b60405173ffffffffffffffffffffffffffffffffffffffff80851660248301528316604482015260648101829052610be29085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401611e92565b6060612b0d8484600085612b15565b949350505050565b606082471015612ba7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401611067565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051612bd09190613827565b60006040518083038185875af1925050503d8060008114612c0d576040519150601f19603f3d011682016040523d82523d6000602084013e612c12565b606091505b5091509150612c2387838387612c2e565b979650505050505050565b60608315612cc4578251600003612cbd5773ffffffffffffffffffffffffffffffffffffffff85163b612cbd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611067565b5081612b0d565b612b0d8383815115612cd95781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110679190612e68565b604051806080016040528060008019168152602001612d556040518060400160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b8152602001612d8d6040518060400160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b8152602001600081525090565b600060208284031215612dac57600080fd5b5035919050565b600060208284031215612dc557600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461289857600080fd5b73ffffffffffffffffffffffffffffffffffffffff81168114611ca657600080fd5b8035612e2281612df5565b919050565b600060208284031215612e3957600080fd5b813561289881612df5565b60005b83811015612e5f578181015183820152602001612e47565b50506000910152565b6020815260008251806020840152612e87816040850160208701612e44565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b77ffffffffffffffffffffffffffffffffffffffffffffffff81168114611ca657600080fd5b600080600060608486031215612ef457600080fd5b8335612eff81612df5565b92506020840135612f0f81612df5565b91506040840135612f1f81612eb9565b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715612fa057612fa0612f2a565b604052919050565b60008083601f840112612fba57600080fd5b50813567ffffffffffffffff811115612fd257600080fd5b6020830191508360208260051b8501011115612fed57600080fd5b9250929050565b60008083601f84011261300657600080fd5b50813567ffffffffffffffff81111561301e57600080fd5b602083019150836020828501011115612fed57600080fd5b6000806000806000806080878903121561304f57600080fd5b863567ffffffffffffffff8082111561306757600080fd5b818901915089601f83011261307b57600080fd5b813560208282111561308f5761308f612f2a565b8160051b61309e828201612f59565b928352848101820192828101908e8511156130b857600080fd5b958301955b848710156130d6578635825295830195908301906130bd565b9b5050508a0135925050808211156130ed57600080fd5b6130f98a838b01612fa8565b9097509550604089013591508082111561311257600080fd5b5061311f89828a01612ff4565b9094509250613132905060608801612e17565b90509295509295509295565b803567ffffffffffffffff81168114612e2257600080fd5b60006020828403121561316857600080fd5b6128988261313e565b6000806000806000806080878903121561318a57600080fd5b86359550602087013567ffffffffffffffff808211156131a957600080fd5b6131b58a838b01612ff4565b909750955060408901359150808211156131ce57600080fd5b506131db89828a01612ff4565b90945092505060608701356131ef81612df5565b809150509295509295509295565b60008060006060848603121561321257600080fd5b833561321d81612df5565b9250602084013591506040840135612f1f81612df5565b6000806000806080858703121561324a57600080fd5b843561325581612df5565b935060208501359250604085013561326c81612df5565b915061327a6060860161313e565b905092959194509250565b600082601f83011261329657600080fd5b813567ffffffffffffffff8111156132b0576132b0612f2a565b6132e160207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601612f59565b8181528460208386010111156132f657600080fd5b816020850160208301376000918101602001919091529392505050565b60008060006060848603121561332857600080fd5b833561333381612df5565b9250602084013567ffffffffffffffff81111561334f57600080fd5b61335b86828701613285565b9250506040840135612f1f81612df5565b60008060006040848603121561338157600080fd5b83359250602084013567ffffffffffffffff808211156133a057600080fd5b818601915086601f8301126133b457600080fd5b8135818111156133c357600080fd5b8760208260061b85010111156133d857600080fd5b6020830194508093505050509250925092565b6000602082840312156133fd57600080fd5b8151801515811461289857600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261347157600080fd5b83018035915067ffffffffffffffff82111561348c57600080fd5b602001915036819003821315612fed57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613501576135016134a1565b5060010190565b60006020828403121561351a57600080fd5b5051919050565b80516020808301519190811015613560577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b805163ffffffff81168114612e2257600080fd5b60008060008060008060c0878903121561359357600080fd5b865195506135a360208801613566565b94506135b160408801613566565b935060608701516135c181612eb9565b60808801519093506135d281612eb9565b915061313260a08801613566565b818103818111156108e4576108e46134a1565b80820281158282048414176108e4576108e46134a1565b808201808211156108e4576108e46134a1565b600081518084526020808501945080840160005b838110156136725781518051885283015177ffffffffffffffffffffffffffffffffffffffffffffffff168388015260409096019590820190600101613631565b509495945050505050565b604081526000613690604083018561361d565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b8381526040602080830182905282820184905260009190859060608501845b878110156137285783356136e781612df5565b73ffffffffffffffffffffffffffffffffffffffff16825267ffffffffffffffff61371385850161313e565b168284015292840192908401906001016136d4565b5098975050505050505050565b6000806080838503121561374857600080fd5b83601f84011261375757600080fd5b6040516060810167ffffffffffffffff828210818311171561377b5761377b612f2a565b81604052829150606086018781111561379357600080fd5b865b818110156137ad578035845260209384019301613795565b50929450913591808311156137c157600080fd5b50506137cf85828601613285565b9150509250929050565b602081526000612898602083018461361d565b600082613822577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b60008251613839818460208701612e44565b919091019291505056fea164736f6c6343000813000a",
+}
+
+var DestinationFeeManagerABI = DestinationFeeManagerMetaData.ABI
+
+var DestinationFeeManagerBin = DestinationFeeManagerMetaData.Bin
+
+func DeployDestinationFeeManager(auth *bind.TransactOpts, backend bind.ContractBackend, _linkAddress common.Address, _nativeAddress common.Address, _verifierAddress common.Address, _rewardManagerAddress common.Address) (common.Address, *types.Transaction, *DestinationFeeManager, error) {
+	parsed, err := DestinationFeeManagerMetaData.GetAbi()
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	if parsed == nil {
+		return common.Address{}, nil, nil, errors.New("GetABI returned nil")
+	}
+
+	address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(DestinationFeeManagerBin), backend, _linkAddress, _nativeAddress, _verifierAddress, _rewardManagerAddress)
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	return address, tx, &DestinationFeeManager{address: address, abi: *parsed, DestinationFeeManagerCaller: DestinationFeeManagerCaller{contract: contract}, DestinationFeeManagerTransactor: DestinationFeeManagerTransactor{contract: contract}, DestinationFeeManagerFilterer: DestinationFeeManagerFilterer{contract: contract}}, nil
+}
+
+type DestinationFeeManager struct {
+	address common.Address
+	abi     abi.ABI
+	DestinationFeeManagerCaller
+	DestinationFeeManagerTransactor
+	DestinationFeeManagerFilterer
+}
+
+type DestinationFeeManagerCaller struct {
+	contract *bind.BoundContract
+}
+
+type DestinationFeeManagerTransactor struct {
+	contract *bind.BoundContract
+}
+
+type DestinationFeeManagerFilterer struct {
+	contract *bind.BoundContract
+}
+
+type DestinationFeeManagerSession struct {
+	Contract     *DestinationFeeManager
+	CallOpts     bind.CallOpts
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationFeeManagerCallerSession struct {
+	Contract *DestinationFeeManagerCaller
+	CallOpts bind.CallOpts
+}
+
+type DestinationFeeManagerTransactorSession struct {
+	Contract     *DestinationFeeManagerTransactor
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationFeeManagerRaw struct {
+	Contract *DestinationFeeManager
+}
+
+type DestinationFeeManagerCallerRaw struct {
+	Contract *DestinationFeeManagerCaller
+}
+
+type DestinationFeeManagerTransactorRaw struct {
+	Contract *DestinationFeeManagerTransactor
+}
+
+func NewDestinationFeeManager(address common.Address, backend bind.ContractBackend) (*DestinationFeeManager, error) {
+	abi, err := abi.JSON(strings.NewReader(DestinationFeeManagerABI))
+	if err != nil {
+		return nil, err
+	}
+	contract, err := bindDestinationFeeManager(address, backend, backend, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManager{address: address, abi: abi, DestinationFeeManagerCaller: DestinationFeeManagerCaller{contract: contract}, DestinationFeeManagerTransactor: DestinationFeeManagerTransactor{contract: contract}, DestinationFeeManagerFilterer: DestinationFeeManagerFilterer{contract: contract}}, nil
+}
+
+func NewDestinationFeeManagerCaller(address common.Address, caller bind.ContractCaller) (*DestinationFeeManagerCaller, error) {
+	contract, err := bindDestinationFeeManager(address, caller, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerCaller{contract: contract}, nil
+}
+
+func NewDestinationFeeManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationFeeManagerTransactor, error) {
+	contract, err := bindDestinationFeeManager(address, nil, transactor, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerTransactor{contract: contract}, nil
+}
+
+func NewDestinationFeeManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationFeeManagerFilterer, error) {
+	contract, err := bindDestinationFeeManager(address, nil, nil, filterer)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerFilterer{contract: contract}, nil
+}
+
+func bindDestinationFeeManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+	parsed, err := DestinationFeeManagerMetaData.GetAbi()
+	if err != nil {
+		return nil, err
+	}
+	return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationFeeManager.Contract.DestinationFeeManagerCaller.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.DestinationFeeManagerTransactor.contract.Transfer(opts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.DestinationFeeManagerTransactor.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationFeeManager.Contract.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.contract.Transfer(opts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) GetFeeAndReward(opts *bind.CallOpts, subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "getFeeAndReward", subscriber, report, quoteAddress)
+
+	if err != nil {
+		return *new(CommonAsset), *new(CommonAsset), *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(CommonAsset)).(*CommonAsset)
+	out1 := *abi.ConvertType(out[1], new(CommonAsset)).(*CommonAsset)
+	out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int)
+
+	return out0, out1, out2, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) GetFeeAndReward(subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) {
+	return _DestinationFeeManager.Contract.GetFeeAndReward(&_DestinationFeeManager.CallOpts, subscriber, report, quoteAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) GetFeeAndReward(subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) {
+	return _DestinationFeeManager.Contract.GetFeeAndReward(&_DestinationFeeManager.CallOpts, subscriber, report, quoteAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) ILinkAddress(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "i_linkAddress")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) ILinkAddress() (common.Address, error) {
+	return _DestinationFeeManager.Contract.ILinkAddress(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) ILinkAddress() (common.Address, error) {
+	return _DestinationFeeManager.Contract.ILinkAddress(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) INativeAddress(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "i_nativeAddress")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) INativeAddress() (common.Address, error) {
+	return _DestinationFeeManager.Contract.INativeAddress(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) INativeAddress() (common.Address, error) {
+	return _DestinationFeeManager.Contract.INativeAddress(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) IRewardManager(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "i_rewardManager")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) IRewardManager() (common.Address, error) {
+	return _DestinationFeeManager.Contract.IRewardManager(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) IRewardManager() (common.Address, error) {
+	return _DestinationFeeManager.Contract.IRewardManager(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) LinkAvailableForPayment(opts *bind.CallOpts) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "linkAvailableForPayment")
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) LinkAvailableForPayment() (*big.Int, error) {
+	return _DestinationFeeManager.Contract.LinkAvailableForPayment(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) LinkAvailableForPayment() (*big.Int, error) {
+	return _DestinationFeeManager.Contract.LinkAvailableForPayment(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "owner")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) Owner() (common.Address, error) {
+	return _DestinationFeeManager.Contract.Owner(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) Owner() (common.Address, error) {
+	return _DestinationFeeManager.Contract.Owner(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) SLinkDeficit(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "s_linkDeficit", arg0)
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SLinkDeficit(arg0 [32]byte) (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SLinkDeficit(&_DestinationFeeManager.CallOpts, arg0)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SLinkDeficit(arg0 [32]byte) (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SLinkDeficit(&_DestinationFeeManager.CallOpts, arg0)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) SNativeSurcharge(opts *bind.CallOpts) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "s_nativeSurcharge")
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SNativeSurcharge() (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SNativeSurcharge(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SNativeSurcharge() (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SNativeSurcharge(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) SSubscriberDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "s_subscriberDiscounts", arg0, arg1, arg2)
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SSubscriberDiscounts(arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SSubscriberDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1, arg2)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SSubscriberDiscounts(arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) {
+	return _DestinationFeeManager.Contract.SSubscriberDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1, arg2)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) SVerifierAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "s_verifierAddressList", arg0)
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SVerifierAddressList(arg0 common.Address) (common.Address, error) {
+	return _DestinationFeeManager.Contract.SVerifierAddressList(&_DestinationFeeManager.CallOpts, arg0)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SVerifierAddressList(arg0 common.Address) (common.Address, error) {
+	return _DestinationFeeManager.Contract.SVerifierAddressList(&_DestinationFeeManager.CallOpts, arg0)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "supportsInterface", interfaceId)
+
+	if err != nil {
+		return *new(bool), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationFeeManager.Contract.SupportsInterface(&_DestinationFeeManager.CallOpts, interfaceId)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationFeeManager.Contract.SupportsInterface(&_DestinationFeeManager.CallOpts, interfaceId)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) {
+	var out []interface{}
+	err := _DestinationFeeManager.contract.Call(opts, &out, "typeAndVersion")
+
+	if err != nil {
+		return *new(string), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+	return out0, err
+
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) TypeAndVersion() (string, error) {
+	return _DestinationFeeManager.Contract.TypeAndVersion(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerCallerSession) TypeAndVersion() (string, error) {
+	return _DestinationFeeManager.Contract.TypeAndVersion(&_DestinationFeeManager.CallOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "acceptOwnership")
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.AcceptOwnership(&_DestinationFeeManager.TransactOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.AcceptOwnership(&_DestinationFeeManager.TransactOpts)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) AddVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "addVerifier", verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) AddVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.AddVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) AddVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.AddVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) PayLinkDeficit(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "payLinkDeficit", configDigest)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) PayLinkDeficit(configDigest [32]byte) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.PayLinkDeficit(&_DestinationFeeManager.TransactOpts, configDigest)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) PayLinkDeficit(configDigest [32]byte) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.PayLinkDeficit(&_DestinationFeeManager.TransactOpts, configDigest)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) ProcessFee(opts *bind.TransactOpts, recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "processFee", recipient, payload, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) ProcessFee(recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.ProcessFee(&_DestinationFeeManager.TransactOpts, recipient, payload, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) ProcessFee(recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.ProcessFee(&_DestinationFeeManager.TransactOpts, recipient, payload, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) ProcessFeeBulk(opts *bind.TransactOpts, poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "processFeeBulk", poolIds, payloads, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) ProcessFeeBulk(poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.ProcessFeeBulk(&_DestinationFeeManager.TransactOpts, poolIds, payloads, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) ProcessFeeBulk(poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.ProcessFeeBulk(&_DestinationFeeManager.TransactOpts, poolIds, payloads, parameterPayload, subscriber)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) RemoveVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "removeVerifier", verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) RemoveVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.RemoveVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) RemoveVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.RemoveVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetFeeRecipients(opts *bind.TransactOpts, configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "setFeeRecipients", configDigest, rewardRecipientAndWeights)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SetFeeRecipients(configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetFeeRecipients(&_DestinationFeeManager.TransactOpts, configDigest, rewardRecipientAndWeights)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetFeeRecipients(configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetFeeRecipients(&_DestinationFeeManager.TransactOpts, configDigest, rewardRecipientAndWeights)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetNativeSurcharge(opts *bind.TransactOpts, surcharge uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "setNativeSurcharge", surcharge)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SetNativeSurcharge(surcharge uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetNativeSurcharge(&_DestinationFeeManager.TransactOpts, surcharge)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetNativeSurcharge(surcharge uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetNativeSurcharge(&_DestinationFeeManager.TransactOpts, surcharge)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetRewardManager(opts *bind.TransactOpts, rewardManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "setRewardManager", rewardManagerAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) SetRewardManager(rewardManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetRewardManager(&_DestinationFeeManager.TransactOpts, rewardManagerAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetRewardManager(rewardManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.SetRewardManager(&_DestinationFeeManager.TransactOpts, rewardManagerAddress)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "transferOwnership", to)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.TransferOwnership(&_DestinationFeeManager.TransactOpts, to)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.TransferOwnership(&_DestinationFeeManager.TransactOpts, to)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) UpdateSubscriberDiscount(opts *bind.TransactOpts, subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "updateSubscriberDiscount", subscriber, feedId, token, discount)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) UpdateSubscriberDiscount(subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.UpdateSubscriberDiscount(&_DestinationFeeManager.TransactOpts, subscriber, feedId, token, discount)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) UpdateSubscriberDiscount(subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.UpdateSubscriberDiscount(&_DestinationFeeManager.TransactOpts, subscriber, feedId, token, discount)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactor) Withdraw(opts *bind.TransactOpts, assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) {
+	return _DestinationFeeManager.contract.Transact(opts, "withdraw", assetAddress, recipient, quantity)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerSession) Withdraw(assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.Withdraw(&_DestinationFeeManager.TransactOpts, assetAddress, recipient, quantity)
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) Withdraw(assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) {
+	return _DestinationFeeManager.Contract.Withdraw(&_DestinationFeeManager.TransactOpts, assetAddress, recipient, quantity)
+}
+
+type DestinationFeeManagerDiscountAppliedIterator struct {
+	Event *DestinationFeeManagerDiscountApplied
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerDiscountAppliedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerDiscountApplied)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerDiscountApplied)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerDiscountAppliedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerDiscountAppliedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerDiscountApplied struct {
+	ConfigDigest    [32]byte
+	Subscriber      common.Address
+	Fee             CommonAsset
+	Reward          CommonAsset
+	AppliedDiscount *big.Int
+	Raw             types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterDiscountApplied(opts *bind.FilterOpts, configDigest [][32]byte, subscriber []common.Address) (*DestinationFeeManagerDiscountAppliedIterator, error) {
+
+	var configDigestRule []interface{}
+	for _, configDigestItem := range configDigest {
+		configDigestRule = append(configDigestRule, configDigestItem)
+	}
+	var subscriberRule []interface{}
+	for _, subscriberItem := range subscriber {
+		subscriberRule = append(subscriberRule, subscriberItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "DiscountApplied", configDigestRule, subscriberRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerDiscountAppliedIterator{contract: _DestinationFeeManager.contract, event: "DiscountApplied", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchDiscountApplied(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerDiscountApplied, configDigest [][32]byte, subscriber []common.Address) (event.Subscription, error) {
+
+	var configDigestRule []interface{}
+	for _, configDigestItem := range configDigest {
+		configDigestRule = append(configDigestRule, configDigestItem)
+	}
+	var subscriberRule []interface{}
+	for _, subscriberItem := range subscriber {
+		subscriberRule = append(subscriberRule, subscriberItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "DiscountApplied", configDigestRule, subscriberRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerDiscountApplied)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "DiscountApplied", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseDiscountApplied(log types.Log) (*DestinationFeeManagerDiscountApplied, error) {
+	event := new(DestinationFeeManagerDiscountApplied)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "DiscountApplied", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerInsufficientLinkIterator struct {
+	Event *DestinationFeeManagerInsufficientLink
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerInsufficientLinkIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerInsufficientLink)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerInsufficientLink)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerInsufficientLinkIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerInsufficientLinkIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerInsufficientLink struct {
+	Rewards []IDestinationRewardManagerFeePayment
+	Raw     types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterInsufficientLink(opts *bind.FilterOpts) (*DestinationFeeManagerInsufficientLinkIterator, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "InsufficientLink")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerInsufficientLinkIterator{contract: _DestinationFeeManager.contract, event: "InsufficientLink", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchInsufficientLink(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerInsufficientLink) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "InsufficientLink")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerInsufficientLink)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "InsufficientLink", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseInsufficientLink(log types.Log) (*DestinationFeeManagerInsufficientLink, error) {
+	event := new(DestinationFeeManagerInsufficientLink)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "InsufficientLink", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerLinkDeficitClearedIterator struct {
+	Event *DestinationFeeManagerLinkDeficitCleared
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerLinkDeficitClearedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerLinkDeficitCleared)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerLinkDeficitCleared)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerLinkDeficitClearedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerLinkDeficitClearedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerLinkDeficitCleared struct {
+	ConfigDigest [32]byte
+	LinkQuantity *big.Int
+	Raw          types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterLinkDeficitCleared(opts *bind.FilterOpts, configDigest [][32]byte) (*DestinationFeeManagerLinkDeficitClearedIterator, error) {
+
+	var configDigestRule []interface{}
+	for _, configDigestItem := range configDigest {
+		configDigestRule = append(configDigestRule, configDigestItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "LinkDeficitCleared", configDigestRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerLinkDeficitClearedIterator{contract: _DestinationFeeManager.contract, event: "LinkDeficitCleared", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchLinkDeficitCleared(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerLinkDeficitCleared, configDigest [][32]byte) (event.Subscription, error) {
+
+	var configDigestRule []interface{}
+	for _, configDigestItem := range configDigest {
+		configDigestRule = append(configDigestRule, configDigestItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "LinkDeficitCleared", configDigestRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerLinkDeficitCleared)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "LinkDeficitCleared", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseLinkDeficitCleared(log types.Log) (*DestinationFeeManagerLinkDeficitCleared, error) {
+	event := new(DestinationFeeManagerLinkDeficitCleared)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "LinkDeficitCleared", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerNativeSurchargeUpdatedIterator struct {
+	Event *DestinationFeeManagerNativeSurchargeUpdated
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerNativeSurchargeUpdatedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerNativeSurchargeUpdated)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerNativeSurchargeUpdated)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerNativeSurchargeUpdatedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerNativeSurchargeUpdatedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerNativeSurchargeUpdated struct {
+	NewSurcharge uint64
+	Raw          types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterNativeSurchargeUpdated(opts *bind.FilterOpts) (*DestinationFeeManagerNativeSurchargeUpdatedIterator, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "NativeSurchargeUpdated")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerNativeSurchargeUpdatedIterator{contract: _DestinationFeeManager.contract, event: "NativeSurchargeUpdated", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchNativeSurchargeUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerNativeSurchargeUpdated) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "NativeSurchargeUpdated")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerNativeSurchargeUpdated)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "NativeSurchargeUpdated", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseNativeSurchargeUpdated(log types.Log) (*DestinationFeeManagerNativeSurchargeUpdated, error) {
+	event := new(DestinationFeeManagerNativeSurchargeUpdated)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "NativeSurchargeUpdated", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerOwnershipTransferRequestedIterator struct {
+	Event *DestinationFeeManagerOwnershipTransferRequested
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerOwnershipTransferRequestedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerOwnershipTransferRequested)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerOwnershipTransferRequested)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerOwnershipTransferRequestedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerOwnershipTransferRequestedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerOwnershipTransferRequested struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferRequestedIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerOwnershipTransferRequestedIterator{contract: _DestinationFeeManager.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerOwnershipTransferRequested)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationFeeManagerOwnershipTransferRequested, error) {
+	event := new(DestinationFeeManagerOwnershipTransferRequested)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerOwnershipTransferredIterator struct {
+	Event *DestinationFeeManagerOwnershipTransferred
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerOwnershipTransferredIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerOwnershipTransferred)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerOwnershipTransferred)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerOwnershipTransferredIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerOwnershipTransferredIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerOwnershipTransferred struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferredIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerOwnershipTransferredIterator{contract: _DestinationFeeManager.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerOwnershipTransferred)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationFeeManagerOwnershipTransferred, error) {
+	event := new(DestinationFeeManagerOwnershipTransferred)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerSubscriberDiscountUpdatedIterator struct {
+	Event *DestinationFeeManagerSubscriberDiscountUpdated
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerSubscriberDiscountUpdated)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerSubscriberDiscountUpdated)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerSubscriberDiscountUpdated struct {
+	Subscriber common.Address
+	FeedId     [32]byte
+	Token      common.Address
+	Discount   uint64
+	Raw        types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterSubscriberDiscountUpdated(opts *bind.FilterOpts, subscriber []common.Address, feedId [][32]byte) (*DestinationFeeManagerSubscriberDiscountUpdatedIterator, error) {
+
+	var subscriberRule []interface{}
+	for _, subscriberItem := range subscriber {
+		subscriberRule = append(subscriberRule, subscriberItem)
+	}
+	var feedIdRule []interface{}
+	for _, feedIdItem := range feedId {
+		feedIdRule = append(feedIdRule, feedIdItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "SubscriberDiscountUpdated", subscriberRule, feedIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerSubscriberDiscountUpdatedIterator{contract: _DestinationFeeManager.contract, event: "SubscriberDiscountUpdated", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchSubscriberDiscountUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerSubscriberDiscountUpdated, subscriber []common.Address, feedId [][32]byte) (event.Subscription, error) {
+
+	var subscriberRule []interface{}
+	for _, subscriberItem := range subscriber {
+		subscriberRule = append(subscriberRule, subscriberItem)
+	}
+	var feedIdRule []interface{}
+	for _, feedIdItem := range feedId {
+		feedIdRule = append(feedIdRule, feedIdItem)
+	}
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "SubscriberDiscountUpdated", subscriberRule, feedIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerSubscriberDiscountUpdated)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "SubscriberDiscountUpdated", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseSubscriberDiscountUpdated(log types.Log) (*DestinationFeeManagerSubscriberDiscountUpdated, error) {
+	event := new(DestinationFeeManagerSubscriberDiscountUpdated)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "SubscriberDiscountUpdated", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationFeeManagerWithdrawIterator struct {
+	Event *DestinationFeeManagerWithdraw
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationFeeManagerWithdrawIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationFeeManagerWithdraw)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationFeeManagerWithdraw)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationFeeManagerWithdrawIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationFeeManagerWithdrawIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationFeeManagerWithdraw struct {
+	AdminAddress common.Address
+	Recipient    common.Address
+	AssetAddress common.Address
+	Quantity     *big.Int
+	Raw          types.Log
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterWithdraw(opts *bind.FilterOpts) (*DestinationFeeManagerWithdrawIterator, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "Withdraw")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationFeeManagerWithdrawIterator{contract: _DestinationFeeManager.contract, event: "Withdraw", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchWithdraw(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerWithdraw) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "Withdraw")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationFeeManagerWithdraw)
+				if err := _DestinationFeeManager.contract.UnpackLog(event, "Withdraw", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseWithdraw(log types.Log) (*DestinationFeeManagerWithdraw, error) {
+	event := new(DestinationFeeManagerWithdraw)
+	if err := _DestinationFeeManager.contract.UnpackLog(event, "Withdraw", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+func (_DestinationFeeManager *DestinationFeeManager) ParseLog(log types.Log) (generated.AbigenLog, error) {
+	switch log.Topics[0] {
+	case _DestinationFeeManager.abi.Events["DiscountApplied"].ID:
+		return _DestinationFeeManager.ParseDiscountApplied(log)
+	case _DestinationFeeManager.abi.Events["InsufficientLink"].ID:
+		return _DestinationFeeManager.ParseInsufficientLink(log)
+	case _DestinationFeeManager.abi.Events["LinkDeficitCleared"].ID:
+		return _DestinationFeeManager.ParseLinkDeficitCleared(log)
+	case _DestinationFeeManager.abi.Events["NativeSurchargeUpdated"].ID:
+		return _DestinationFeeManager.ParseNativeSurchargeUpdated(log)
+	case _DestinationFeeManager.abi.Events["OwnershipTransferRequested"].ID:
+		return _DestinationFeeManager.ParseOwnershipTransferRequested(log)
+	case _DestinationFeeManager.abi.Events["OwnershipTransferred"].ID:
+		return _DestinationFeeManager.ParseOwnershipTransferred(log)
+	case _DestinationFeeManager.abi.Events["SubscriberDiscountUpdated"].ID:
+		return _DestinationFeeManager.ParseSubscriberDiscountUpdated(log)
+	case _DestinationFeeManager.abi.Events["Withdraw"].ID:
+		return _DestinationFeeManager.ParseWithdraw(log)
+
+	default:
+		return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0])
+	}
+}
+
+func (DestinationFeeManagerDiscountApplied) Topic() common.Hash {
+	return common.HexToHash("0x88b15eb682210089cddf967648e2cb2a4535aeadc8f8f36050922e33c04e7125")
+}
+
+func (DestinationFeeManagerInsufficientLink) Topic() common.Hash {
+	return common.HexToHash("0xf52e5907b69d97c33392936c12d78b494463b78c5b72df50b4c497eee5720b67")
+}
+
+func (DestinationFeeManagerLinkDeficitCleared) Topic() common.Hash {
+	return common.HexToHash("0x843f0b103e50b42b08f9d30f12f961845a6d02623730872e24644899c0dd9895")
+}
+
+func (DestinationFeeManagerNativeSurchargeUpdated) Topic() common.Hash {
+	return common.HexToHash("0x08f7c0d17932ddb8523bc06754d42ff19ebc77d76a8b9bfde02c28ab1ed3d639")
+}
+
+func (DestinationFeeManagerOwnershipTransferRequested) Topic() common.Hash {
+	return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278")
+}
+
+func (DestinationFeeManagerOwnershipTransferred) Topic() common.Hash {
+	return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0")
+}
+
+func (DestinationFeeManagerSubscriberDiscountUpdated) Topic() common.Hash {
+	return common.HexToHash("0x5eba5a8afa39780f0f99b6cbeb95f3da6a7040ca00abd46bdc91a0a060134139")
+}
+
+func (DestinationFeeManagerWithdraw) Topic() common.Hash {
+	return common.HexToHash("0x7ff78a71698bdb18dcca96f52ab25e0a1b146fb6a49adf8e6845299e49021f29")
+}
+
+func (_DestinationFeeManager *DestinationFeeManager) Address() common.Address {
+	return _DestinationFeeManager.address
+}
+
+type DestinationFeeManagerInterface interface {
+	GetFeeAndReward(opts *bind.CallOpts, subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error)
+
+	ILinkAddress(opts *bind.CallOpts) (common.Address, error)
+
+	INativeAddress(opts *bind.CallOpts) (common.Address, error)
+
+	IRewardManager(opts *bind.CallOpts) (common.Address, error)
+
+	LinkAvailableForPayment(opts *bind.CallOpts) (*big.Int, error)
+
+	Owner(opts *bind.CallOpts) (common.Address, error)
+
+	SLinkDeficit(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error)
+
+	SNativeSurcharge(opts *bind.CallOpts) (*big.Int, error)
+
+	SSubscriberDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error)
+
+	SVerifierAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error)
+
+	SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error)
+
+	TypeAndVersion(opts *bind.CallOpts) (string, error)
+
+	AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error)
+
+	AddVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error)
+
+	PayLinkDeficit(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error)
+
+	ProcessFee(opts *bind.TransactOpts, recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error)
+
+	ProcessFeeBulk(opts *bind.TransactOpts, poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error)
+
+	RemoveVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error)
+
+	SetFeeRecipients(opts *bind.TransactOpts, configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error)
+
+	SetNativeSurcharge(opts *bind.TransactOpts, surcharge uint64) (*types.Transaction, error)
+
+	SetRewardManager(opts *bind.TransactOpts, rewardManagerAddress common.Address) (*types.Transaction, error)
+
+	TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error)
+
+	UpdateSubscriberDiscount(opts *bind.TransactOpts, subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error)
+
+	Withdraw(opts *bind.TransactOpts, assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error)
+
+	FilterDiscountApplied(opts *bind.FilterOpts, configDigest [][32]byte, subscriber []common.Address) (*DestinationFeeManagerDiscountAppliedIterator, error)
+
+	WatchDiscountApplied(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerDiscountApplied, configDigest [][32]byte, subscriber []common.Address) (event.Subscription, error)
+
+	ParseDiscountApplied(log types.Log) (*DestinationFeeManagerDiscountApplied, error)
+
+	FilterInsufficientLink(opts *bind.FilterOpts) (*DestinationFeeManagerInsufficientLinkIterator, error)
+
+	WatchInsufficientLink(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerInsufficientLink) (event.Subscription, error)
+
+	ParseInsufficientLink(log types.Log) (*DestinationFeeManagerInsufficientLink, error)
+
+	FilterLinkDeficitCleared(opts *bind.FilterOpts, configDigest [][32]byte) (*DestinationFeeManagerLinkDeficitClearedIterator, error)
+
+	WatchLinkDeficitCleared(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerLinkDeficitCleared, configDigest [][32]byte) (event.Subscription, error)
+
+	ParseLinkDeficitCleared(log types.Log) (*DestinationFeeManagerLinkDeficitCleared, error)
+
+	FilterNativeSurchargeUpdated(opts *bind.FilterOpts) (*DestinationFeeManagerNativeSurchargeUpdatedIterator, error)
+
+	WatchNativeSurchargeUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerNativeSurchargeUpdated) (event.Subscription, error)
+
+	ParseNativeSurchargeUpdated(log types.Log) (*DestinationFeeManagerNativeSurchargeUpdated, error)
+
+	FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferRequestedIterator, error)
+
+	WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferRequested(log types.Log) (*DestinationFeeManagerOwnershipTransferRequested, error)
+
+	FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferredIterator, error)
+
+	WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferred(log types.Log) (*DestinationFeeManagerOwnershipTransferred, error)
+
+	FilterSubscriberDiscountUpdated(opts *bind.FilterOpts, subscriber []common.Address, feedId [][32]byte) (*DestinationFeeManagerSubscriberDiscountUpdatedIterator, error)
+
+	WatchSubscriberDiscountUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerSubscriberDiscountUpdated, subscriber []common.Address, feedId [][32]byte) (event.Subscription, error)
+
+	ParseSubscriberDiscountUpdated(log types.Log) (*DestinationFeeManagerSubscriberDiscountUpdated, error)
+
+	FilterWithdraw(opts *bind.FilterOpts) (*DestinationFeeManagerWithdrawIterator, error)
+
+	WatchWithdraw(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerWithdraw) (event.Subscription, error)
+
+	ParseWithdraw(log types.Log) (*DestinationFeeManagerWithdraw, error)
+
+	ParseLog(log types.Log) (generated.AbigenLog, error)
+
+	Address() common.Address
+}
diff --git a/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go b/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go
new file mode 100644
index 00000000000..989482fc0ec
--- /dev/null
+++ b/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go
@@ -0,0 +1,1434 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package destination_reward_manager
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"strings"
+
+	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated"
+)
+
+var (
+	_ = errors.New
+	_ = big.NewInt
+	_ = strings.NewReader
+	_ = ethereum.NotFound
+	_ = bind.Bind
+	_ = common.Big1
+	_ = types.BloomLookup
+	_ = event.NewSubscription
+	_ = abi.ConvertType
+)
+
+type CommonAddressAndWeight struct {
+	Addr   common.Address
+	Weight uint64
+}
+
+type IDestinationRewardManagerFeePayment struct {
+	PoolId [32]byte
+	Amount *big.Int
+}
+
+var DestinationRewardManagerMetaData = &bind.MetaData{
+	ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"linkAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPoolId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPoolLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidWeights\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newFeeManagerAddress\",\"type\":\"address\"}],\"name\":\"FeeManagerUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"indexed\":false,\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"payments\",\"type\":\"tuple[]\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"payer\",\"type\":\"address\"}],\"name\":\"FeePaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"newRewardRecipients\",\"type\":\"tuple[]\"}],\"name\":\"RewardRecipientsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"RewardsClaimed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newFeeManagerAddress\",\"type\":\"address\"}],\"name\":\"addFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"poolIds\",\"type\":\"bytes32[]\"}],\"name\":\"claimRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"startIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endIndex\",\"type\":\"uint256\"}],\"name\":\"getAvailableRewardPoolIds\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_linkAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"payments\",\"type\":\"tuple[]\"},{\"internalType\":\"address\",\"name\":\"payer\",\"type\":\"address\"}],\"name\":\"onFeePaid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"recipients\",\"type\":\"address[]\"}],\"name\":\"payRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"feeManagerAddress\",\"type\":\"address\"}],\"name\":\"removeFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_feeManagerAddressList\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_registeredPoolIds\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_rewardRecipientWeights\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_rewardRecipientWeightsSet\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_totalRewardRecipientFees\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_totalRewardRecipientFeesLastClaimedAmounts\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"rewardRecipientAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setRewardRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"newRewardRecipients\",\"type\":\"tuple[]\"}],\"name\":\"updateRewardRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
+	Bin: "0x60a06040523480156200001157600080fd5b506040516200244c3803806200244c8339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163e6c4247b60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b60805161224a62000202600039600081816103bb0152818161107601526112b1015261224a6000f3fe608060405234801561001057600080fd5b506004361061016c5760003560e01c806360122608116100cd5780639604d05f11610081578063cd5f729211610066578063cd5f7292146103a3578063ea4b861b146103b6578063f2fde38b146103dd57600080fd5b80639604d05f1461035a578063b0d9fa191461039057600080fd5b80638115c9cc116100b25780638115c9cc146102dd5780638ac85a5c146102f05780638da5cb5b1461031b57600080fd5b806360122608146102aa57806379ba5097146102d557600080fd5b806339ee81e1116101245780634944832f116101095780634944832f146102615780634d32208414610274578063592562011461028757600080fd5b806339ee81e114610213578063472264751461024157600080fd5b806314060f231161015557806314060f23146101ae578063181f5a77146101c15780631f2d32c31461020057600080fd5b806301ffc9a7146101715780630f3c34d114610199575b600080fd5b61018461017f366004611b3d565b6103f0565b60405190151581526020015b60405180910390f35b6101ac6101a7366004611bfd565b610651565b005b6101ac6101bc366004611cef565b61065f565b604080518082018252601381527f5265776172644d616e6167657220312e302e3000000000000000000000000000602082015290516101909190611d5f565b6101ac61020e366004611dd9565b610821565b610233610221366004611df4565b60026020526000908152604090205481565b604051908152602001610190565b61025461024f366004611e0d565b610959565b6040516101909190611e40565b6101ac61026f366004611cef565b610ae3565b6101ac610282366004611e84565b610c2c565b610184610295366004611df4565b60056020526000908152604090205460ff1681565b6102336102b8366004611f03565b600360209081526000928352604080842090915290825290205481565b6101ac610d6b565b6101ac6102eb366004611dd9565b610e6d565b6102336102fe366004611f03565b600460209081526000928352604080842090915290825290205481565b60005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610190565b610335610368366004611dd9565b60076020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b6101ac61039e366004611f2f565b610f1f565b6102336103b1366004611df4565b6110df565b6103357f000000000000000000000000000000000000000000000000000000000000000081565b6101ac6103eb366004611dd9565b611100565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f0f3c34d100000000000000000000000000000000000000000000000000000000148061048357507fffffffff0000000000000000000000000000000000000000000000000000000082167f14060f2300000000000000000000000000000000000000000000000000000000145b806104cf57507fffffffff0000000000000000000000000000000000000000000000000000000082167f4944832f00000000000000000000000000000000000000000000000000000000145b8061051b57507fffffffff0000000000000000000000000000000000000000000000000000000082167f4d32208400000000000000000000000000000000000000000000000000000000145b8061056757507fffffffff0000000000000000000000000000000000000000000000000000000082167f1f2d32c300000000000000000000000000000000000000000000000000000000145b806105b357507fffffffff0000000000000000000000000000000000000000000000000000000082167f8115c9cc00000000000000000000000000000000000000000000000000000000145b806105ff57507fffffffff0000000000000000000000000000000000000000000000000000000082167f4722647500000000000000000000000000000000000000000000000000000000145b8061064b57507fffffffff0000000000000000000000000000000000000000000000000000000082167fb0d9fa1900000000000000000000000000000000000000000000000000000000145b92915050565b61065b3382611114565b5050565b60005473ffffffffffffffffffffffffffffffffffffffff1633148015906106ab57503360008181526007602052604090205473ffffffffffffffffffffffffffffffffffffffff1614155b156106e2576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600081900361071d576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526005602052604090205460ff1615610766576040517f0afa7ee800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006805460018181019092557ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01849055600084815260056020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690911790556107e2838383670de0b6b3a76400006112df565b827f8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe68383604051610814929190611f9b565b60405180910390a2505050565b6108296114b6565b73ffffffffffffffffffffffffffffffffffffffff8116610876576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81811660009081526007602052604090205416156108d5576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526007602090815260409182902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000168417905590519182527fe45f5e140399b0a7e12971ab020724b828fbed8ac408c420884dc7d1bbe506b4910160405180910390a150565b600654606090600081841161096e5783610970565b815b9050808511156109ac576040517fa22caccc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109b88683612032565b67ffffffffffffffff8111156109d0576109d0611b7f565b6040519080825280602002602001820160405280156109f9578160200160208202803683370190505b5090506000865b83811015610ad657600060068281548110610a1d57610a1d612045565b600091825260208083209091015480835260048252604080842073ffffffffffffffffffffffffffffffffffffffff8f16855290925291205490915015610ac5576000818152600260209081526040808320546003835281842073ffffffffffffffffffffffffffffffffffffffff8f168552909252909120548114610ac35781858580600101965081518110610ab657610ab6612045565b6020026020010181815250505b505b50610acf81612074565b9050610a00565b5090979650505050505050565b610aeb6114b6565b604080516001808252818301909252600091602080830190803683370190505090508381600081518110610b2157610b21612045565b6020026020010181815250506000805b83811015610bde576000858583818110610b4d57610b4d612045565b610b639260206040909202019081019150611dd9565b600088815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff85168452909152902054909150610bc7878785818110610bab57610bab612045565b610bc19260206040909202019081019150611dd9565b86611114565b50929092019150610bd781612074565b9050610b31565b50610beb858585846112df565b847f8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe68585604051610c1d929190611f9b565b60405180910390a25050505050565b82610c4c60005473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015610c9e57506000818152600460209081526040808320338452909152902054155b15610cd5576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516001808252818301909252600091602080830190803683370190505090508481600081518110610d0b57610d0b612045565b60200260200101818152505060005b83811015610d6357610d52858583818110610d3757610d37612045565b9050602002016020810190610d4c9190611dd9565b83611114565b50610d5c81612074565b9050610d1a565b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610df1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610e756114b6565b73ffffffffffffffffffffffffffffffffffffffff81811660009081526007602052604090205416610ed3576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600090815260076020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b3360008181526007602052604090205473ffffffffffffffffffffffffffffffffffffffff1614610f7c576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b8381101561105b57848482818110610f9a57610f9a612045565b9050604002016020016020810190610fb291906120d4565b77ffffffffffffffffffffffffffffffffffffffffffffffff1660026000878785818110610fe257610fe2612045565b604090810292909201358352506020820192909252016000208054909101905584848281811061101457611014612045565b905060400201602001602081019061102c91906120d4565b77ffffffffffffffffffffffffffffffffffffffffffffffff16820191508061105490612074565b9050610f80565b5061109e73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016833084611539565b7fa1cc025ea76bacce5d740ee4bc331899375dc2c5f2ab33933aaacbd9ba001b668484846040516110d1939291906120ef565b60405180910390a150505050565b600681815481106110ef57600080fd5b600091825260209091200154905081565b6111086114b6565b6111118161161b565b50565b60008060005b835181101561129057600084828151811061113757611137612045565b6020026020010151905060006002600083815260200190815260200160002054905080600003611168575050611280565b600082815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8b16808552908352818420548685526004845282852091855292528220549083039190670de0b6b3a7640000908302049050806000036111d15750505050611280565b600084815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8d16808552925290912084905588519682019689908790811061121c5761121c612045565b60200260200101517f989969655bc1d593922527fe85d71347bb8e12fa423cc71f362dd8ef7cb10ef283604051611273919077ffffffffffffffffffffffffffffffffffffffffffffffff91909116815260200190565b60405180910390a3505050505b61128981612074565b905061111a565b5080156112d8576112d873ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168583611710565b9392505050565b61133a8383808060200260200160405190810160405280939291908181526020016000905b828210156113305761132160408302860136819003810190612176565b81526020019060010190611304565b505050505061176b565b15611371576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b8381101561147557600085858381811061139157611391612045565b90506040020160200160208101906113a991906121d1565b67ffffffffffffffff16905060008686848181106113c9576113c9612045565b6113df9260206040909202019081019150611dd9565b905073ffffffffffffffffffffffffffffffffffffffff811661142e576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600088815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff909416835292905220819055919091019061146e81612074565b9050611375565b508181146114af576040517f84677ce800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611537576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610de8565b565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526116159085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152611822565b50505050565b3373ffffffffffffffffffffffffffffffffffffffff82160361169a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610de8565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60405173ffffffffffffffffffffffffffffffffffffffff83166024820152604481018290526117669084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401611593565b505050565b6000805b82518110156118195760006117858260016121ec565b90505b8351811015611810578381815181106117a3576117a3612045565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff168483815181106117d7576117d7612045565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1603611808575060019392505050565b600101611788565b5060010161176f565b50600092915050565b6000611884826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661192e9092919063ffffffff16565b80519091501561176657808060200190518101906118a291906121ff565b611766576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610de8565b606061193d8484600085611945565b949350505050565b6060824710156119d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610de8565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611a009190612221565b60006040518083038185875af1925050503d8060008114611a3d576040519150601f19603f3d011682016040523d82523d6000602084013e611a42565b606091505b5091509150611a5387838387611a5e565b979650505050505050565b60608315611af4578251600003611aed5773ffffffffffffffffffffffffffffffffffffffff85163b611aed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610de8565b508161193d565b61193d8383815115611b095781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610de89190611d5f565b600060208284031215611b4f57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146112d857600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611bf557611bf5611b7f565b604052919050565b60006020808385031215611c1057600080fd5b823567ffffffffffffffff80821115611c2857600080fd5b818501915085601f830112611c3c57600080fd5b813581811115611c4e57611c4e611b7f565b8060051b9150611c5f848301611bae565b8181529183018401918481019088841115611c7957600080fd5b938501935b83851015611c9757843582529385019390850190611c7e565b98975050505050505050565b60008083601f840112611cb557600080fd5b50813567ffffffffffffffff811115611ccd57600080fd5b6020830191508360208260061b8501011115611ce857600080fd5b9250929050565b600080600060408486031215611d0457600080fd5b83359250602084013567ffffffffffffffff811115611d2257600080fd5b611d2e86828701611ca3565b9497909650939450505050565b60005b83811015611d56578181015183820152602001611d3e565b50506000910152565b6020815260008251806020840152611d7e816040850160208701611d3b565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611dd457600080fd5b919050565b600060208284031215611deb57600080fd5b6112d882611db0565b600060208284031215611e0657600080fd5b5035919050565b600080600060608486031215611e2257600080fd5b611e2b84611db0565b95602085013595506040909401359392505050565b6020808252825182820181905260009190848201906040850190845b81811015611e7857835183529284019291840191600101611e5c565b50909695505050505050565b600080600060408486031215611e9957600080fd5b83359250602084013567ffffffffffffffff80821115611eb857600080fd5b818601915086601f830112611ecc57600080fd5b813581811115611edb57600080fd5b8760208260051b8501011115611ef057600080fd5b6020830194508093505050509250925092565b60008060408385031215611f1657600080fd5b82359150611f2660208401611db0565b90509250929050565b600080600060408486031215611f4457600080fd5b833567ffffffffffffffff811115611f5b57600080fd5b611f6786828701611ca3565b9094509250611f7a905060208501611db0565b90509250925092565b803567ffffffffffffffff81168114611dd457600080fd5b6020808252818101839052600090604080840186845b87811015610ad65773ffffffffffffffffffffffffffffffffffffffff611fd783611db0565b16835267ffffffffffffffff611fee868401611f83565b16838601529183019190830190600101611fb1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561064b5761064b612003565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036120a5576120a5612003565b5060010190565b803577ffffffffffffffffffffffffffffffffffffffffffffffff81168114611dd457600080fd5b6000602082840312156120e657600080fd5b6112d8826120ac565b60408082528181018490526000908560608401835b8781101561214b5782358252602077ffffffffffffffffffffffffffffffffffffffffffffffff6121368286016120ac565b16908301529183019190830190600101612104565b5080935050505073ffffffffffffffffffffffffffffffffffffffff83166020830152949350505050565b60006040828403121561218857600080fd5b6040516040810181811067ffffffffffffffff821117156121ab576121ab611b7f565b6040526121b783611db0565b81526121c560208401611f83565b60208201529392505050565b6000602082840312156121e357600080fd5b6112d882611f83565b8082018082111561064b5761064b612003565b60006020828403121561221157600080fd5b815180151581146112d857600080fd5b60008251612233818460208701611d3b565b919091019291505056fea164736f6c6343000813000a",
+}
+
+var DestinationRewardManagerABI = DestinationRewardManagerMetaData.ABI
+
+var DestinationRewardManagerBin = DestinationRewardManagerMetaData.Bin
+
+func DeployDestinationRewardManager(auth *bind.TransactOpts, backend bind.ContractBackend, linkAddress common.Address) (common.Address, *types.Transaction, *DestinationRewardManager, error) {
+	parsed, err := DestinationRewardManagerMetaData.GetAbi()
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	if parsed == nil {
+		return common.Address{}, nil, nil, errors.New("GetABI returned nil")
+	}
+
+	address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(DestinationRewardManagerBin), backend, linkAddress)
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	return address, tx, &DestinationRewardManager{address: address, abi: *parsed, DestinationRewardManagerCaller: DestinationRewardManagerCaller{contract: contract}, DestinationRewardManagerTransactor: DestinationRewardManagerTransactor{contract: contract}, DestinationRewardManagerFilterer: DestinationRewardManagerFilterer{contract: contract}}, nil
+}
+
+type DestinationRewardManager struct {
+	address common.Address
+	abi     abi.ABI
+	DestinationRewardManagerCaller
+	DestinationRewardManagerTransactor
+	DestinationRewardManagerFilterer
+}
+
+type DestinationRewardManagerCaller struct {
+	contract *bind.BoundContract
+}
+
+type DestinationRewardManagerTransactor struct {
+	contract *bind.BoundContract
+}
+
+type DestinationRewardManagerFilterer struct {
+	contract *bind.BoundContract
+}
+
+type DestinationRewardManagerSession struct {
+	Contract     *DestinationRewardManager
+	CallOpts     bind.CallOpts
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationRewardManagerCallerSession struct {
+	Contract *DestinationRewardManagerCaller
+	CallOpts bind.CallOpts
+}
+
+type DestinationRewardManagerTransactorSession struct {
+	Contract     *DestinationRewardManagerTransactor
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationRewardManagerRaw struct {
+	Contract *DestinationRewardManager
+}
+
+type DestinationRewardManagerCallerRaw struct {
+	Contract *DestinationRewardManagerCaller
+}
+
+type DestinationRewardManagerTransactorRaw struct {
+	Contract *DestinationRewardManagerTransactor
+}
+
+func NewDestinationRewardManager(address common.Address, backend bind.ContractBackend) (*DestinationRewardManager, error) {
+	abi, err := abi.JSON(strings.NewReader(DestinationRewardManagerABI))
+	if err != nil {
+		return nil, err
+	}
+	contract, err := bindDestinationRewardManager(address, backend, backend, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManager{address: address, abi: abi, DestinationRewardManagerCaller: DestinationRewardManagerCaller{contract: contract}, DestinationRewardManagerTransactor: DestinationRewardManagerTransactor{contract: contract}, DestinationRewardManagerFilterer: DestinationRewardManagerFilterer{contract: contract}}, nil
+}
+
+func NewDestinationRewardManagerCaller(address common.Address, caller bind.ContractCaller) (*DestinationRewardManagerCaller, error) {
+	contract, err := bindDestinationRewardManager(address, caller, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerCaller{contract: contract}, nil
+}
+
+func NewDestinationRewardManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationRewardManagerTransactor, error) {
+	contract, err := bindDestinationRewardManager(address, nil, transactor, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerTransactor{contract: contract}, nil
+}
+
+func NewDestinationRewardManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationRewardManagerFilterer, error) {
+	contract, err := bindDestinationRewardManager(address, nil, nil, filterer)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerFilterer{contract: contract}, nil
+}
+
+func bindDestinationRewardManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+	parsed, err := DestinationRewardManagerMetaData.GetAbi()
+	if err != nil {
+		return nil, err
+	}
+	return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationRewardManager.Contract.DestinationRewardManagerCaller.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.DestinationRewardManagerTransactor.contract.Transfer(opts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.DestinationRewardManagerTransactor.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationRewardManager.Contract.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.contract.Transfer(opts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) GetAvailableRewardPoolIds(opts *bind.CallOpts, recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "getAvailableRewardPoolIds", recipient, startIndex, endIndex)
+
+	if err != nil {
+		return *new([][32]byte), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new([][32]byte)).(*[][32]byte)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) GetAvailableRewardPoolIds(recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) {
+	return _DestinationRewardManager.Contract.GetAvailableRewardPoolIds(&_DestinationRewardManager.CallOpts, recipient, startIndex, endIndex)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) GetAvailableRewardPoolIds(recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) {
+	return _DestinationRewardManager.Contract.GetAvailableRewardPoolIds(&_DestinationRewardManager.CallOpts, recipient, startIndex, endIndex)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) ILinkAddress(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "i_linkAddress")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) ILinkAddress() (common.Address, error) {
+	return _DestinationRewardManager.Contract.ILinkAddress(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) ILinkAddress() (common.Address, error) {
+	return _DestinationRewardManager.Contract.ILinkAddress(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "owner")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) Owner() (common.Address, error) {
+	return _DestinationRewardManager.Contract.Owner(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) Owner() (common.Address, error) {
+	return _DestinationRewardManager.Contract.Owner(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) SFeeManagerAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_feeManagerAddressList", arg0)
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SFeeManagerAddressList(arg0 common.Address) (common.Address, error) {
+	return _DestinationRewardManager.Contract.SFeeManagerAddressList(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SFeeManagerAddressList(arg0 common.Address) (common.Address, error) {
+	return _DestinationRewardManager.Contract.SFeeManagerAddressList(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) SRegisteredPoolIds(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_registeredPoolIds", arg0)
+
+	if err != nil {
+		return *new([32]byte), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SRegisteredPoolIds(arg0 *big.Int) ([32]byte, error) {
+	return _DestinationRewardManager.Contract.SRegisteredPoolIds(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRegisteredPoolIds(arg0 *big.Int) ([32]byte, error) {
+	return _DestinationRewardManager.Contract.SRegisteredPoolIds(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) SRewardRecipientWeights(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_rewardRecipientWeights", arg0, arg1)
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SRewardRecipientWeights(arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.SRewardRecipientWeights(&_DestinationRewardManager.CallOpts, arg0, arg1)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRewardRecipientWeights(arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.SRewardRecipientWeights(&_DestinationRewardManager.CallOpts, arg0, arg1)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) SRewardRecipientWeightsSet(opts *bind.CallOpts, arg0 [32]byte) (bool, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_rewardRecipientWeightsSet", arg0)
+
+	if err != nil {
+		return *new(bool), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SRewardRecipientWeightsSet(arg0 [32]byte) (bool, error) {
+	return _DestinationRewardManager.Contract.SRewardRecipientWeightsSet(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRewardRecipientWeightsSet(arg0 [32]byte) (bool, error) {
+	return _DestinationRewardManager.Contract.SRewardRecipientWeightsSet(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) STotalRewardRecipientFees(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_totalRewardRecipientFees", arg0)
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) STotalRewardRecipientFees(arg0 [32]byte) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.STotalRewardRecipientFees(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) STotalRewardRecipientFees(arg0 [32]byte) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.STotalRewardRecipientFees(&_DestinationRewardManager.CallOpts, arg0)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) STotalRewardRecipientFeesLastClaimedAmounts(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "s_totalRewardRecipientFeesLastClaimedAmounts", arg0, arg1)
+
+	if err != nil {
+		return *new(*big.Int), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) STotalRewardRecipientFeesLastClaimedAmounts(arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.STotalRewardRecipientFeesLastClaimedAmounts(&_DestinationRewardManager.CallOpts, arg0, arg1)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) STotalRewardRecipientFeesLastClaimedAmounts(arg0 [32]byte, arg1 common.Address) (*big.Int, error) {
+	return _DestinationRewardManager.Contract.STotalRewardRecipientFeesLastClaimedAmounts(&_DestinationRewardManager.CallOpts, arg0, arg1)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "supportsInterface", interfaceId)
+
+	if err != nil {
+		return *new(bool), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationRewardManager.Contract.SupportsInterface(&_DestinationRewardManager.CallOpts, interfaceId)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationRewardManager.Contract.SupportsInterface(&_DestinationRewardManager.CallOpts, interfaceId)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) {
+	var out []interface{}
+	err := _DestinationRewardManager.contract.Call(opts, &out, "typeAndVersion")
+
+	if err != nil {
+		return *new(string), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+	return out0, err
+
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) TypeAndVersion() (string, error) {
+	return _DestinationRewardManager.Contract.TypeAndVersion(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerCallerSession) TypeAndVersion() (string, error) {
+	return _DestinationRewardManager.Contract.TypeAndVersion(&_DestinationRewardManager.CallOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "acceptOwnership")
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.AcceptOwnership(&_DestinationRewardManager.TransactOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.AcceptOwnership(&_DestinationRewardManager.TransactOpts)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) AddFeeManager(opts *bind.TransactOpts, newFeeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "addFeeManager", newFeeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) AddFeeManager(newFeeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.AddFeeManager(&_DestinationRewardManager.TransactOpts, newFeeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) AddFeeManager(newFeeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.AddFeeManager(&_DestinationRewardManager.TransactOpts, newFeeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) ClaimRewards(opts *bind.TransactOpts, poolIds [][32]byte) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "claimRewards", poolIds)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) ClaimRewards(poolIds [][32]byte) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.ClaimRewards(&_DestinationRewardManager.TransactOpts, poolIds)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) ClaimRewards(poolIds [][32]byte) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.ClaimRewards(&_DestinationRewardManager.TransactOpts, poolIds)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) OnFeePaid(opts *bind.TransactOpts, payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "onFeePaid", payments, payer)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) OnFeePaid(payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.OnFeePaid(&_DestinationRewardManager.TransactOpts, payments, payer)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) OnFeePaid(payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.OnFeePaid(&_DestinationRewardManager.TransactOpts, payments, payer)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) PayRecipients(opts *bind.TransactOpts, poolId [32]byte, recipients []common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "payRecipients", poolId, recipients)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) PayRecipients(poolId [32]byte, recipients []common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.PayRecipients(&_DestinationRewardManager.TransactOpts, poolId, recipients)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) PayRecipients(poolId [32]byte, recipients []common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.PayRecipients(&_DestinationRewardManager.TransactOpts, poolId, recipients)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) RemoveFeeManager(opts *bind.TransactOpts, feeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "removeFeeManager", feeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) RemoveFeeManager(feeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.RemoveFeeManager(&_DestinationRewardManager.TransactOpts, feeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) RemoveFeeManager(feeManagerAddress common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.RemoveFeeManager(&_DestinationRewardManager.TransactOpts, feeManagerAddress)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) SetRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "setRewardRecipients", poolId, rewardRecipientAndWeights)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) SetRewardRecipients(poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.SetRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, rewardRecipientAndWeights)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) SetRewardRecipients(poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.SetRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, rewardRecipientAndWeights)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "transferOwnership", to)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.TransferOwnership(&_DestinationRewardManager.TransactOpts, to)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.TransferOwnership(&_DestinationRewardManager.TransactOpts, to)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactor) UpdateRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.contract.Transact(opts, "updateRewardRecipients", poolId, newRewardRecipients)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerSession) UpdateRewardRecipients(poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.UpdateRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, newRewardRecipients)
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) UpdateRewardRecipients(poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationRewardManager.Contract.UpdateRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, newRewardRecipients)
+}
+
+type DestinationRewardManagerFeeManagerUpdatedIterator struct {
+	Event *DestinationRewardManagerFeeManagerUpdated
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerFeeManagerUpdatedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerFeeManagerUpdated)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerFeeManagerUpdated)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerFeeManagerUpdatedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerFeeManagerUpdatedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerFeeManagerUpdated struct {
+	NewFeeManagerAddress common.Address
+	Raw                  types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterFeeManagerUpdated(opts *bind.FilterOpts) (*DestinationRewardManagerFeeManagerUpdatedIterator, error) {
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "FeeManagerUpdated")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerFeeManagerUpdatedIterator{contract: _DestinationRewardManager.contract, event: "FeeManagerUpdated", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchFeeManagerUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeeManagerUpdated) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "FeeManagerUpdated")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerFeeManagerUpdated)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "FeeManagerUpdated", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseFeeManagerUpdated(log types.Log) (*DestinationRewardManagerFeeManagerUpdated, error) {
+	event := new(DestinationRewardManagerFeeManagerUpdated)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "FeeManagerUpdated", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationRewardManagerFeePaidIterator struct {
+	Event *DestinationRewardManagerFeePaid
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerFeePaidIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerFeePaid)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerFeePaid)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerFeePaidIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerFeePaidIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerFeePaid struct {
+	Payments []IDestinationRewardManagerFeePayment
+	Payer    common.Address
+	Raw      types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterFeePaid(opts *bind.FilterOpts) (*DestinationRewardManagerFeePaidIterator, error) {
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "FeePaid")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerFeePaidIterator{contract: _DestinationRewardManager.contract, event: "FeePaid", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchFeePaid(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeePaid) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "FeePaid")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerFeePaid)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "FeePaid", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseFeePaid(log types.Log) (*DestinationRewardManagerFeePaid, error) {
+	event := new(DestinationRewardManagerFeePaid)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "FeePaid", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationRewardManagerOwnershipTransferRequestedIterator struct {
+	Event *DestinationRewardManagerOwnershipTransferRequested
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerOwnershipTransferRequestedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerOwnershipTransferRequested)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerOwnershipTransferRequested)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerOwnershipTransferRequestedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerOwnershipTransferRequestedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerOwnershipTransferRequested struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferRequestedIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerOwnershipTransferRequestedIterator{contract: _DestinationRewardManager.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerOwnershipTransferRequested)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationRewardManagerOwnershipTransferRequested, error) {
+	event := new(DestinationRewardManagerOwnershipTransferRequested)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationRewardManagerOwnershipTransferredIterator struct {
+	Event *DestinationRewardManagerOwnershipTransferred
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerOwnershipTransferredIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerOwnershipTransferred)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerOwnershipTransferred)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerOwnershipTransferredIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerOwnershipTransferredIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerOwnershipTransferred struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferredIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerOwnershipTransferredIterator{contract: _DestinationRewardManager.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerOwnershipTransferred)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationRewardManagerOwnershipTransferred, error) {
+	event := new(DestinationRewardManagerOwnershipTransferred)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationRewardManagerRewardRecipientsUpdatedIterator struct {
+	Event *DestinationRewardManagerRewardRecipientsUpdated
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerRewardRecipientsUpdatedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerRewardRecipientsUpdated)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerRewardRecipientsUpdated)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerRewardRecipientsUpdatedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerRewardRecipientsUpdatedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerRewardRecipientsUpdated struct {
+	PoolId              [32]byte
+	NewRewardRecipients []CommonAddressAndWeight
+	Raw                 types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterRewardRecipientsUpdated(opts *bind.FilterOpts, poolId [][32]byte) (*DestinationRewardManagerRewardRecipientsUpdatedIterator, error) {
+
+	var poolIdRule []interface{}
+	for _, poolIdItem := range poolId {
+		poolIdRule = append(poolIdRule, poolIdItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "RewardRecipientsUpdated", poolIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerRewardRecipientsUpdatedIterator{contract: _DestinationRewardManager.contract, event: "RewardRecipientsUpdated", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchRewardRecipientsUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardRecipientsUpdated, poolId [][32]byte) (event.Subscription, error) {
+
+	var poolIdRule []interface{}
+	for _, poolIdItem := range poolId {
+		poolIdRule = append(poolIdRule, poolIdItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "RewardRecipientsUpdated", poolIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerRewardRecipientsUpdated)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardRecipientsUpdated", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseRewardRecipientsUpdated(log types.Log) (*DestinationRewardManagerRewardRecipientsUpdated, error) {
+	event := new(DestinationRewardManagerRewardRecipientsUpdated)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardRecipientsUpdated", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationRewardManagerRewardsClaimedIterator struct {
+	Event *DestinationRewardManagerRewardsClaimed
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationRewardManagerRewardsClaimedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationRewardManagerRewardsClaimed)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationRewardManagerRewardsClaimed)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationRewardManagerRewardsClaimedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationRewardManagerRewardsClaimedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationRewardManagerRewardsClaimed struct {
+	PoolId    [32]byte
+	Recipient common.Address
+	Quantity  *big.Int
+	Raw       types.Log
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterRewardsClaimed(opts *bind.FilterOpts, poolId [][32]byte, recipient []common.Address) (*DestinationRewardManagerRewardsClaimedIterator, error) {
+
+	var poolIdRule []interface{}
+	for _, poolIdItem := range poolId {
+		poolIdRule = append(poolIdRule, poolIdItem)
+	}
+	var recipientRule []interface{}
+	for _, recipientItem := range recipient {
+		recipientRule = append(recipientRule, recipientItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "RewardsClaimed", poolIdRule, recipientRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationRewardManagerRewardsClaimedIterator{contract: _DestinationRewardManager.contract, event: "RewardsClaimed", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchRewardsClaimed(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardsClaimed, poolId [][32]byte, recipient []common.Address) (event.Subscription, error) {
+
+	var poolIdRule []interface{}
+	for _, poolIdItem := range poolId {
+		poolIdRule = append(poolIdRule, poolIdItem)
+	}
+	var recipientRule []interface{}
+	for _, recipientItem := range recipient {
+		recipientRule = append(recipientRule, recipientItem)
+	}
+
+	logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "RewardsClaimed", poolIdRule, recipientRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationRewardManagerRewardsClaimed)
+				if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardsClaimed", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseRewardsClaimed(log types.Log) (*DestinationRewardManagerRewardsClaimed, error) {
+	event := new(DestinationRewardManagerRewardsClaimed)
+	if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardsClaimed", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+func (_DestinationRewardManager *DestinationRewardManager) ParseLog(log types.Log) (generated.AbigenLog, error) {
+	switch log.Topics[0] {
+	case _DestinationRewardManager.abi.Events["FeeManagerUpdated"].ID:
+		return _DestinationRewardManager.ParseFeeManagerUpdated(log)
+	case _DestinationRewardManager.abi.Events["FeePaid"].ID:
+		return _DestinationRewardManager.ParseFeePaid(log)
+	case _DestinationRewardManager.abi.Events["OwnershipTransferRequested"].ID:
+		return _DestinationRewardManager.ParseOwnershipTransferRequested(log)
+	case _DestinationRewardManager.abi.Events["OwnershipTransferred"].ID:
+		return _DestinationRewardManager.ParseOwnershipTransferred(log)
+	case _DestinationRewardManager.abi.Events["RewardRecipientsUpdated"].ID:
+		return _DestinationRewardManager.ParseRewardRecipientsUpdated(log)
+	case _DestinationRewardManager.abi.Events["RewardsClaimed"].ID:
+		return _DestinationRewardManager.ParseRewardsClaimed(log)
+
+	default:
+		return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0])
+	}
+}
+
+func (DestinationRewardManagerFeeManagerUpdated) Topic() common.Hash {
+	return common.HexToHash("0xe45f5e140399b0a7e12971ab020724b828fbed8ac408c420884dc7d1bbe506b4")
+}
+
+func (DestinationRewardManagerFeePaid) Topic() common.Hash {
+	return common.HexToHash("0xa1cc025ea76bacce5d740ee4bc331899375dc2c5f2ab33933aaacbd9ba001b66")
+}
+
+func (DestinationRewardManagerOwnershipTransferRequested) Topic() common.Hash {
+	return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278")
+}
+
+func (DestinationRewardManagerOwnershipTransferred) Topic() common.Hash {
+	return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0")
+}
+
+func (DestinationRewardManagerRewardRecipientsUpdated) Topic() common.Hash {
+	return common.HexToHash("0x8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe6")
+}
+
+func (DestinationRewardManagerRewardsClaimed) Topic() common.Hash {
+	return common.HexToHash("0x989969655bc1d593922527fe85d71347bb8e12fa423cc71f362dd8ef7cb10ef2")
+}
+
+func (_DestinationRewardManager *DestinationRewardManager) Address() common.Address {
+	return _DestinationRewardManager.address
+}
+
+type DestinationRewardManagerInterface interface {
+	GetAvailableRewardPoolIds(opts *bind.CallOpts, recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error)
+
+	ILinkAddress(opts *bind.CallOpts) (common.Address, error)
+
+	Owner(opts *bind.CallOpts) (common.Address, error)
+
+	SFeeManagerAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error)
+
+	SRegisteredPoolIds(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error)
+
+	SRewardRecipientWeights(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error)
+
+	SRewardRecipientWeightsSet(opts *bind.CallOpts, arg0 [32]byte) (bool, error)
+
+	STotalRewardRecipientFees(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error)
+
+	STotalRewardRecipientFeesLastClaimedAmounts(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error)
+
+	SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error)
+
+	TypeAndVersion(opts *bind.CallOpts) (string, error)
+
+	AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error)
+
+	AddFeeManager(opts *bind.TransactOpts, newFeeManagerAddress common.Address) (*types.Transaction, error)
+
+	ClaimRewards(opts *bind.TransactOpts, poolIds [][32]byte) (*types.Transaction, error)
+
+	OnFeePaid(opts *bind.TransactOpts, payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error)
+
+	PayRecipients(opts *bind.TransactOpts, poolId [32]byte, recipients []common.Address) (*types.Transaction, error)
+
+	RemoveFeeManager(opts *bind.TransactOpts, feeManagerAddress common.Address) (*types.Transaction, error)
+
+	SetRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error)
+
+	TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error)
+
+	UpdateRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error)
+
+	FilterFeeManagerUpdated(opts *bind.FilterOpts) (*DestinationRewardManagerFeeManagerUpdatedIterator, error)
+
+	WatchFeeManagerUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeeManagerUpdated) (event.Subscription, error)
+
+	ParseFeeManagerUpdated(log types.Log) (*DestinationRewardManagerFeeManagerUpdated, error)
+
+	FilterFeePaid(opts *bind.FilterOpts) (*DestinationRewardManagerFeePaidIterator, error)
+
+	WatchFeePaid(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeePaid) (event.Subscription, error)
+
+	ParseFeePaid(log types.Log) (*DestinationRewardManagerFeePaid, error)
+
+	FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferRequestedIterator, error)
+
+	WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferRequested(log types.Log) (*DestinationRewardManagerOwnershipTransferRequested, error)
+
+	FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferredIterator, error)
+
+	WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferred(log types.Log) (*DestinationRewardManagerOwnershipTransferred, error)
+
+	FilterRewardRecipientsUpdated(opts *bind.FilterOpts, poolId [][32]byte) (*DestinationRewardManagerRewardRecipientsUpdatedIterator, error)
+
+	WatchRewardRecipientsUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardRecipientsUpdated, poolId [][32]byte) (event.Subscription, error)
+
+	ParseRewardRecipientsUpdated(log types.Log) (*DestinationRewardManagerRewardRecipientsUpdated, error)
+
+	FilterRewardsClaimed(opts *bind.FilterOpts, poolId [][32]byte, recipient []common.Address) (*DestinationRewardManagerRewardsClaimedIterator, error)
+
+	WatchRewardsClaimed(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardsClaimed, poolId [][32]byte, recipient []common.Address) (event.Subscription, error)
+
+	ParseRewardsClaimed(log types.Log) (*DestinationRewardManagerRewardsClaimed, error)
+
+	ParseLog(log types.Log) (generated.AbigenLog, error)
+
+	Address() common.Address
+}
diff --git a/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go b/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go
new file mode 100644
index 00000000000..2fa48b7249c
--- /dev/null
+++ b/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go
@@ -0,0 +1,1576 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package destination_verifier
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"strings"
+
+	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated"
+)
+
+var (
+	_ = errors.New
+	_ = big.NewInt
+	_ = strings.NewReader
+	_ = ethereum.NotFound
+	_ = bind.Bind
+	_ = common.Big1
+	_ = types.BloomLookup
+	_ = event.NewSubscription
+	_ = abi.ConvertType
+)
+
+type CommonAddressAndWeight struct {
+	Addr   common.Address
+	Weight uint64
+}
+
+var DestinationVerifierMetaData = &bind.MetaData{
+	ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierProxy\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadActivationTime\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadVerification\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"}],\"name\":\"DonConfigAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DonConfigDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeeManagerInvalid\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"rsLength\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"ssLength\",\"type\":\"uint256\"}],\"name\":\"MismatchedSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"VerifierProxyInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAccessController\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAccessController\",\"type\":\"address\"}],\"name\":\"AccessControllerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isActive\",\"type\":\"bool\"}],\"name\":\"ConfigActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"}],\"name\":\"ConfigRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldFeeManager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newFeeManager\",\"type\":\"address\"}],\"name\":\"FeeManagerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requester\",\"type\":\"address\"}],\"name\":\"ReportVerified\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_verifierProxy\",\"outputs\":[{\"internalType\":\"contractIDestinationVerifierProxy\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"removeLatestConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_accessController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_feeManager\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessController\",\"type\":\"address\"}],\"name\":\"setAccessController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"donConfigIndex\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isActive\",\"type\":\"bool\"}],\"name\":\"setConfigActive\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"activationTime\",\"type\":\"uint32\"}],\"name\":\"setConfigWithActivationTime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"feeManager\",\"type\":\"address\"}],\"name\":\"setFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signedReport\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"signedReports\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"verifyBulk\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"}]",
+	Bin: "0x60a06040523480156200001157600080fd5b506040516200379d3803806200379d8339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b60805161359b62000202600039600081816102d70152818161062a0152611064015261359b6000f3fe6080604052600436106100f35760003560e01c80638da5cb5b1161008a578063d7c72e4e11610059578063d7c72e4e146102f9578063f08391d814610319578063f2fde38b14610339578063f9c7bf771461035957600080fd5b80638da5cb5b1461025857806394ba284614610283578063af4fed24146102b0578063b97455c7146102c557600080fd5b8063453ec61b116100c6578063453ec61b146101e1578063472d35b9146102035780635ad72fae1461022357806379ba50971461024357600080fd5b806301ffc9a7146100f8578063181f5a771461012d578063294d2bb11461017c57806338416b5b1461018f575b600080fd5b34801561010457600080fd5b50610118610113366004612622565b610379565b60405190151581526020015b60405180910390f35b34801561013957600080fd5b5060408051808201909152601981527f44657374696e6174696f6e566572696669657220312e302e300000000000000060208201525b60405161012491906126cf565b61016f61018a366004612754565b610626565b34801561019b57600080fd5b506004546101bc9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b3480156101ed57600080fd5b506102016101fc3660046129e3565b610873565b005b34801561020f57600080fd5b5061020161021e366004612a57565b61097b565b34801561022f57600080fd5b5061020161023e366004612a80565b610c5c565b34801561024f57600080fd5b50610201610d72565b34801561026457600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101bc565b34801561028f57600080fd5b506005546101bc9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156102bc57600080fd5b50610201610e6f565b3480156102d157600080fd5b506101bc7f000000000000000000000000000000000000000000000000000000000000000081565b61030c610307366004612ab0565b611060565b6040516101249190612b33565b34801561032557600080fd5b50610201610334366004612a57565b611352565b34801561034557600080fd5b50610201610354366004612a57565b6113d9565b34801561036557600080fd5b50610201610374366004612bc5565b6113ed565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f294d2bb100000000000000000000000000000000000000000000000000000000148061040c57507fffffffff0000000000000000000000000000000000000000000000000000000082167fd7c72e4e00000000000000000000000000000000000000000000000000000000145b8061045857507fffffffff0000000000000000000000000000000000000000000000000000000082167f94ba284600000000000000000000000000000000000000000000000000000000145b806104a457507fffffffff0000000000000000000000000000000000000000000000000000000082167f38416b5b00000000000000000000000000000000000000000000000000000000145b806104f057507fffffffff0000000000000000000000000000000000000000000000000000000082167f453ec61b00000000000000000000000000000000000000000000000000000000145b8061053c57507fffffffff0000000000000000000000000000000000000000000000000000000082167ff9c7bf7700000000000000000000000000000000000000000000000000000000145b8061058857507fffffffff0000000000000000000000000000000000000000000000000000000082167f472d35b900000000000000000000000000000000000000000000000000000000145b806105d457507fffffffff0000000000000000000000000000000000000000000000000000000082167ff08391d800000000000000000000000000000000000000000000000000000000145b8061062057507fffffffff0000000000000000000000000000000000000000000000000000000082167f5ad72fae00000000000000000000000000000000000000000000000000000000145b92915050565b60607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163314610697576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600554829073ffffffffffffffffffffffffffffffffffffffff16801580159061075657506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf8906107139085906000903690600401612c95565b602060405180830381865afa158015610730573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107549190612cce565b155b1561078d576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008061079b8a8a886114ab565b600454919350915073ffffffffffffffffffffffffffffffffffffffff168015610864578073ffffffffffffffffffffffffffffffffffffffff166386968cfd34848e8e8e8e8e6040518863ffffffff1660e01b815260040161080396959493929190612ceb565b6000604051808303818588803b15801561081c57600080fd5b505af19350505050801561082e575060015b610864576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50909998505050505050505050565b82518260ff16806000036108b3576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156108fd576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b610908816003612d71565b8211610960578161091a826003612d71565b610925906001612d88565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016108f4565b6109686119b0565b61097485858542611a33565b5050505050565b6109836119b0565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f86968cfd00000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610a0d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a319190612cce565b1580610ae857506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f3690750900000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610ac2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae69190612cce565b155b80610b9e57506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff65df96200000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610b78573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b9c9190612cce565b155b15610bd5576040517f8238941900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f0391015b60405180910390a15050565b610c646119b0565b6003548210610c9f576040517f5a0d6fe200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060038381548110610cb457610cb4612d9b565b600091825260209182902001805484151579010000000000000000000000000000000000000000000000000081027fffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffff909216919091178083556040805191811b7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000168252938101919091529092507f90186a1e77b498ec417ea88bd026cae00d7043c357cc45221777623bda582dd4910160405180910390a1505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610df3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016108f4565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610e776119b0565b600354600003610eb3576040517f5a0d6fe200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003805460009190610ec790600190612dca565b81548110610ed757610ed7612d9b565b600091825260209182902060408051608081018252929091015480821b7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001683527801000000000000000000000000000000000000000000000000810460ff9081169484019490945279010000000000000000000000000000000000000000000000000081049093161515908201527a01000000000000000000000000000000000000000000000000000090910463ffffffff166060820152600380549192509080610fa557610fa5612ddd565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffff00000000000000000000000000000000000000000000000000000000000016905501905580516040517f970fd8f3ebdd9a271080aacf9807a5c709be0b448e4047a6fc212b8cc165368d91611055917fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000091909116815260200190565b60405180910390a150565b60607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1633146110d1576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600554829073ffffffffffffffffffffffffffffffffffffffff16801580159061119057506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf89061114d9085906000903690600401612c95565b602060405180830381865afa15801561116a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061118e9190612cce565b155b156111c7576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008767ffffffffffffffff8111156111e2576111e26127d5565b60405190808252806020026020018201604052801561121557816020015b60608152602001906001900390816112005790505b50905060008867ffffffffffffffff811115611233576112336127d5565b60405190808252806020026020018201604052801561125c578160200160208202803683370190505b50905060005b898110156112ee5760008061129a8d8d8581811061128257611282612d9b565b90506020028101906112949190612e0c565b8b6114ab565b91509150818584815181106112b1576112b1612d9b565b6020026020010181905250808484815181106112cf576112cf612d9b565b6020026020010181815250505050806112e790612e71565b9050611262565b5060045473ffffffffffffffffffffffffffffffffffffffff168015610864578073ffffffffffffffffffffffffffffffffffffffff16633690750934848e8e8e8e8e6040518863ffffffff1660e01b815260040161080396959493929190612ea9565b61135a6119b0565b6005805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b69101610c50565b6113e16119b0565b6113ea816120eb565b50565b83518360ff168060000361142d576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115611472576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044016108f4565b61147d816003612d71565b821161148f578161091a826003612d71565b6114976119b0565b6114a386868686611a33565b505050505050565b6060600080808080806114c0898b018b6130db565b94509450945094509450815183511461151257825182516040517ff0d31408000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016108f4565b825160000361154d576040517fc7af40f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008480519060200120866040516020016115699291906131b6565b6040516020818303038152906040528051906020012090506000845167ffffffffffffffff81111561159d5761159d6127d5565b6040519080825280602002602001820160405280156115c6578160200160208202803683370190505b50905060005b85518110156116d4576001838583602081106115ea576115ea612d9b565b6115f791901a601b6131f2565b88848151811061160957611609612d9b565b602002602001015188858151811061162357611623612d9b565b602002602001015160405160008152602001604052604051611661949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611683573d6000803e3d6000fd5b5050506020604051035182828151811061169f5761169f612d9b565b73ffffffffffffffffffffffffffffffffffffffff909216602092830291909101909101526116cd81612e71565b90506115cc565b506116de816121e0565b15611715576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006117208761228f565b9050600061172d826122b5565b80519091507fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001661178a576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80604001516117c5576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806020015160ff16835111611806576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b84518110156119245784818151811061182557611825612d9b565b6020026020010151836000015160405160200161189592919060609290921b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001682527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166014820152602c0190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291815281516020928301206000818152600290935291205490925060ff16611914576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61191d81612e71565b905061180a565b5061192e8961320b565b60405173ffffffffffffffffffffffffffffffffffffffff8f1681527f58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc59060200160405180910390a25051969d7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009097169c50959a5050505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611a31576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016108f4565b565b83518360ff1680600003611a73576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115611ab8576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044016108f4565b611ac3816003612d71565b8211611ad5578161091a826003612d71565b611add6119b0565b611ae6866121e0565b15611b1d576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b428363ffffffff161115611b5d576040517f0114c7e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b7686600060018951611b719190612dca565b61243a565b60008686604051602001611b8b929190613250565b60405160208183030381529060405280519060200120905060005b8751811015611d3257600073ffffffffffffffffffffffffffffffffffffffff16888281518110611bd957611bd9612d9b565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611c2e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600260008a8481518110611c4657611c46612d9b565b602002602001015185604051602001611cb292919060609290921b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001682527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166014820152602c0190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291815281516020928301208352908201929092520160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055611d2b81612e71565b9050611ba6565b506003548015801590611d96575063ffffffff85166003611d54600184612dca565b81548110611d6457611d64612d9b565b6000918252602090912001547a010000000000000000000000000000000000000000000000000000900463ffffffff16115b15611dcd576040517f0114c7e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600081118015611e4b57507fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000082166003611e08600184612dca565b81548110611e1857611e18612d9b565b60009182526020909120015460401b7fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016145b15611ea6576040517fc0178c860000000000000000000000000000000000000000000000000000000081527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000831660048201526024016108f4565b855115611f3757600480546040517ff65df96200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169163f65df96291611f049186918b910161332e565b600060405180830381600087803b158015611f1e57600080fd5b505af1158015611f32573d6000803e3d6000fd5b505050505b604080516080810182527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000841680825260ff808b1660208401908152600184860181815263ffffffff808d166060880190815260038054948501815560005296517fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b90930180549451925197519091167a010000000000000000000000000000000000000000000000000000027fffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffff97151579010000000000000000000000000000000000000000000000000002979097167fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff929095167801000000000000000000000000000000000000000000000000027fffffffffffffff0000000000000000000000000000000000000000000000000090941692881c929092179290921791909116919091179290921790915590517f2d763a674a99583454a287d792819ffb9ff7e791c23e7745a082701136ce336c906120d9908b908b908b90613371565b60405180910390a25050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff82160361216a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016108f4565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000805b82518110156122865760006121fa826001612d88565b90505b835181101561227d5783818151811061221857612218612d9b565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1684838151811061224857612248612d9b565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603612275575060019392505050565b6001016121fd565b506001016121e4565b50600092915050565b600080828060200190518101906122a691906133dd565b63ffffffff1695945050505050565b6040805160808101825260008082526020820181905291810182905260608101919091526040805160808101825260008082526020820181905291810182905260608101919091526003545b80156124335761231081613420565b9050836003828154811061232657612326612d9b565b6000918252602090912001547a010000000000000000000000000000000000000000000000000000900463ffffffff161161242e576003818154811061236e5761236e612d9b565b600091825260209182902060408051608081018252929091015480821b7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001683527801000000000000000000000000000000000000000000000000810460ff9081169484019490945279010000000000000000000000000000000000000000000000000081049093161515908201527a01000000000000000000000000000000000000000000000000000090910463ffffffff1660608201529150612433565b612301565b5092915050565b818180820361244a575050505050565b60008560026124598787613455565b6124639190613475565b61246d9087613504565b8151811061247d5761247d612d9b565b602002602001015190505b8183136125fc575b8073ffffffffffffffffffffffffffffffffffffffff168684815181106124b9576124b9612d9b565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1610156124ef57826124e78161352c565b935050612490565b85828151811061250157612501612d9b565b602002602001015173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16101561254e57816125468161355d565b9250506124ef565b8183136125f75785828151811061256757612567612d9b565b602002602001015186848151811061258157612581612d9b565b602002602001015187858151811061259b5761259b612d9b565b602002602001018885815181106125b4576125b4612d9b565b73ffffffffffffffffffffffffffffffffffffffff938416602091820292909201015291169052826125e58161352c565b93505081806125f39061355d565b9250505b612488565b8185121561260f5761260f86868461243a565b838312156114a3576114a386848661243a565b60006020828403121561263457600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461266457600080fd5b9392505050565b6000815180845260005b8181101561269157602081850181015186830182015201612675565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000612664602083018461266b565b60008083601f8401126126f457600080fd5b50813567ffffffffffffffff81111561270c57600080fd5b60208301915083602082850101111561272457600080fd5b9250929050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461274f57600080fd5b919050565b60008060008060006060868803121561276c57600080fd5b853567ffffffffffffffff8082111561278457600080fd5b61279089838a016126e2565b909750955060208801359150808211156127a957600080fd5b506127b6888289016126e2565b90945092506127c990506040870161272b565b90509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612827576128276127d5565b60405290565b6040516060810167ffffffffffffffff81118282101715612827576128276127d5565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715612897576128976127d5565b604052919050565b600067ffffffffffffffff8211156128b9576128b96127d5565b5060051b60200190565b600082601f8301126128d457600080fd5b813560206128e96128e48361289f565b612850565b82815260059290921b8401810191818101908684111561290857600080fd5b8286015b8481101561292a5761291d8161272b565b835291830191830161290c565b509695505050505050565b803560ff8116811461274f57600080fd5b600082601f83011261295757600080fd5b813560206129676128e48361289f565b82815260069290921b8401810191818101908684111561298657600080fd5b8286015b8481101561292a57604081890312156129a35760008081fd5b6129ab612804565b6129b48261272b565b81528482013567ffffffffffffffff811681146129d15760008081fd5b8186015283529183019160400161298a565b6000806000606084860312156129f857600080fd5b833567ffffffffffffffff80821115612a1057600080fd5b612a1c878388016128c3565b9450612a2a60208701612935565b93506040860135915080821115612a4057600080fd5b50612a4d86828701612946565b9150509250925092565b600060208284031215612a6957600080fd5b6126648261272b565b80151581146113ea57600080fd5b60008060408385031215612a9357600080fd5b823591506020830135612aa581612a72565b809150509250929050565b600080600080600060608688031215612ac857600080fd5b853567ffffffffffffffff80821115612ae057600080fd5b818801915088601f830112612af457600080fd5b813581811115612b0357600080fd5b8960208260051b8501011115612b1857600080fd5b6020928301975095509087013590808211156127a957600080fd5b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015612ba6577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452612b9485835161266b565b94509285019290850190600101612b5a565b5092979650505050505050565b63ffffffff811681146113ea57600080fd5b60008060008060808587031215612bdb57600080fd5b843567ffffffffffffffff80821115612bf357600080fd5b612bff888389016128c3565b9550612c0d60208801612935565b94506040870135915080821115612c2357600080fd5b50612c3087828801612946565b9250506060850135612c4181612bb3565b939692955090935050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff84168152604060208201526000612cc5604083018486612c4c565b95945050505050565b600060208284031215612ce057600080fd5b815161266481612a72565b868152608060208201526000612d05608083018789612c4c565b8281036040840152612d18818688612c4c565b91505073ffffffffffffffffffffffffffffffffffffffff83166060830152979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761062057610620612d42565b8082018082111561062057610620612d42565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b8181038181111561062057610620612d42565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112612e4157600080fd5b83018035915067ffffffffffffffff821115612e5c57600080fd5b60200191503681900382131561272457600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612ea257612ea2612d42565b5060010190565b6080808252875190820181905260009060209060a0840190828b01845b82811015612ee257815184529284019290840190600101612ec6565b50505083810382850152878152818101600589901b820183018a60005b8b811015612faa577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301845281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18e3603018112612f6057600080fd5b8d01868101903567ffffffffffffffff811115612f7c57600080fd5b803603821315612f8b57600080fd5b612f96858284612c4c565b958801959450505090850190600101612eff565b50508581036040870152612fbf81898b612c4c565b945050505050612fe7606083018473ffffffffffffffffffffffffffffffffffffffff169052565b979650505050505050565b600082601f83011261300357600080fd5b813567ffffffffffffffff81111561301d5761301d6127d5565b61304e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601612850565b81815284602083860101111561306357600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261309157600080fd5b813560206130a16128e48361289f565b82815260059290921b840181019181810190868411156130c057600080fd5b8286015b8481101561292a57803583529183019183016130c4565b600080600080600060e086880312156130f357600080fd5b86601f87011261310257600080fd5b61310a61282d565b80606088018981111561311c57600080fd5b885b8181101561313657803584526020938401930161311e565b5090965035905067ffffffffffffffff8082111561315357600080fd5b61315f89838a01612ff2565b9550608088013591508082111561317557600080fd5b61318189838a01613080565b945060a088013591508082111561319757600080fd5b506131a488828901613080565b9598949750929560c001359392505050565b828152600060208083018460005b60038110156131e1578151835291830191908301906001016131c4565b505050506080820190509392505050565b60ff818116838216019081111561062057610620612d42565b8051602080830151919081101561324a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b825160009082906020808701845b8381101561329057815173ffffffffffffffffffffffffffffffffffffffff168552938201939082019060010161325e565b5050505060f89390931b7fff000000000000000000000000000000000000000000000000000000000000001683525050600101919050565b600081518084526020808501945080840160005b83811015613323578151805173ffffffffffffffffffffffffffffffffffffffff16885283015167ffffffffffffffff1683880152604090960195908201906001016132dc565b509495945050505050565b7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008316815260406020820152600061336960408301846132c8565b949350505050565b606080825284519082018190526000906020906080840190828801845b828110156133c057815173ffffffffffffffffffffffffffffffffffffffff168452928401929084019060010161338e565b50505060ff8616828501528381036040850152612fe781866132c8565b6000806000606084860312156133f257600080fd5b83519250602084015161340481612bb3565b604085015190925061341581612bb3565b809150509250925092565b60008161342f5761342f612d42565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b818103600083128015838313168383128216171561243357612433612d42565b6000826134ab577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f8000000000000000000000000000000000000000000000000000000000000000831416156134ff576134ff612d42565b500590565b808201828112600083128015821682158216171561352457613524612d42565b505092915050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612ea257612ea2612d42565b60007f8000000000000000000000000000000000000000000000000000000000000000820361342f5761342f612d4256fea164736f6c6343000813000a",
+}
+
+var DestinationVerifierABI = DestinationVerifierMetaData.ABI
+
+var DestinationVerifierBin = DestinationVerifierMetaData.Bin
+
+func DeployDestinationVerifier(auth *bind.TransactOpts, backend bind.ContractBackend, verifierProxy common.Address) (common.Address, *types.Transaction, *DestinationVerifier, error) {
+	parsed, err := DestinationVerifierMetaData.GetAbi()
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	if parsed == nil {
+		return common.Address{}, nil, nil, errors.New("GetABI returned nil")
+	}
+
+	address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(DestinationVerifierBin), backend, verifierProxy)
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	return address, tx, &DestinationVerifier{address: address, abi: *parsed, DestinationVerifierCaller: DestinationVerifierCaller{contract: contract}, DestinationVerifierTransactor: DestinationVerifierTransactor{contract: contract}, DestinationVerifierFilterer: DestinationVerifierFilterer{contract: contract}}, nil
+}
+
+type DestinationVerifier struct {
+	address common.Address
+	abi     abi.ABI
+	DestinationVerifierCaller
+	DestinationVerifierTransactor
+	DestinationVerifierFilterer
+}
+
+type DestinationVerifierCaller struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierTransactor struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierFilterer struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierSession struct {
+	Contract     *DestinationVerifier
+	CallOpts     bind.CallOpts
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationVerifierCallerSession struct {
+	Contract *DestinationVerifierCaller
+	CallOpts bind.CallOpts
+}
+
+type DestinationVerifierTransactorSession struct {
+	Contract     *DestinationVerifierTransactor
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationVerifierRaw struct {
+	Contract *DestinationVerifier
+}
+
+type DestinationVerifierCallerRaw struct {
+	Contract *DestinationVerifierCaller
+}
+
+type DestinationVerifierTransactorRaw struct {
+	Contract *DestinationVerifierTransactor
+}
+
+func NewDestinationVerifier(address common.Address, backend bind.ContractBackend) (*DestinationVerifier, error) {
+	abi, err := abi.JSON(strings.NewReader(DestinationVerifierABI))
+	if err != nil {
+		return nil, err
+	}
+	contract, err := bindDestinationVerifier(address, backend, backend, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifier{address: address, abi: abi, DestinationVerifierCaller: DestinationVerifierCaller{contract: contract}, DestinationVerifierTransactor: DestinationVerifierTransactor{contract: contract}, DestinationVerifierFilterer: DestinationVerifierFilterer{contract: contract}}, nil
+}
+
+func NewDestinationVerifierCaller(address common.Address, caller bind.ContractCaller) (*DestinationVerifierCaller, error) {
+	contract, err := bindDestinationVerifier(address, caller, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierCaller{contract: contract}, nil
+}
+
+func NewDestinationVerifierTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationVerifierTransactor, error) {
+	contract, err := bindDestinationVerifier(address, nil, transactor, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierTransactor{contract: contract}, nil
+}
+
+func NewDestinationVerifierFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationVerifierFilterer, error) {
+	contract, err := bindDestinationVerifier(address, nil, nil, filterer)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierFilterer{contract: contract}, nil
+}
+
+func bindDestinationVerifier(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+	parsed, err := DestinationVerifierMetaData.GetAbi()
+	if err != nil {
+		return nil, err
+	}
+	return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationVerifier.Contract.DestinationVerifierCaller.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationVerifier *DestinationVerifierRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.DestinationVerifierTransactor.contract.Transfer(opts)
+}
+
+func (_DestinationVerifier *DestinationVerifierRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.DestinationVerifierTransactor.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationVerifier.Contract.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.contract.Transfer(opts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) IVerifierProxy(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "i_verifierProxy")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) IVerifierProxy() (common.Address, error) {
+	return _DestinationVerifier.Contract.IVerifierProxy(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) IVerifierProxy() (common.Address, error) {
+	return _DestinationVerifier.Contract.IVerifierProxy(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "owner")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) Owner() (common.Address, error) {
+	return _DestinationVerifier.Contract.Owner(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) Owner() (common.Address, error) {
+	return _DestinationVerifier.Contract.Owner(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) SAccessController(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "s_accessController")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SAccessController() (common.Address, error) {
+	return _DestinationVerifier.Contract.SAccessController(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) SAccessController() (common.Address, error) {
+	return _DestinationVerifier.Contract.SAccessController(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) SFeeManager(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "s_feeManager")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SFeeManager() (common.Address, error) {
+	return _DestinationVerifier.Contract.SFeeManager(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) SFeeManager() (common.Address, error) {
+	return _DestinationVerifier.Contract.SFeeManager(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "supportsInterface", interfaceId)
+
+	if err != nil {
+		return *new(bool), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationVerifier.Contract.SupportsInterface(&_DestinationVerifier.CallOpts, interfaceId)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationVerifier.Contract.SupportsInterface(&_DestinationVerifier.CallOpts, interfaceId)
+}
+
+func (_DestinationVerifier *DestinationVerifierCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) {
+	var out []interface{}
+	err := _DestinationVerifier.contract.Call(opts, &out, "typeAndVersion")
+
+	if err != nil {
+		return *new(string), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) TypeAndVersion() (string, error) {
+	return _DestinationVerifier.Contract.TypeAndVersion(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierCallerSession) TypeAndVersion() (string, error) {
+	return _DestinationVerifier.Contract.TypeAndVersion(&_DestinationVerifier.CallOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "acceptOwnership")
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.AcceptOwnership(&_DestinationVerifier.TransactOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.AcceptOwnership(&_DestinationVerifier.TransactOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) RemoveLatestConfig(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "removeLatestConfig")
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) RemoveLatestConfig() (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.RemoveLatestConfig(&_DestinationVerifier.TransactOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) RemoveLatestConfig() (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.RemoveLatestConfig(&_DestinationVerifier.TransactOpts)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) SetAccessController(opts *bind.TransactOpts, accessController common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "setAccessController", accessController)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SetAccessController(accessController common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetAccessController(&_DestinationVerifier.TransactOpts, accessController)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) SetAccessController(accessController common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetAccessController(&_DestinationVerifier.TransactOpts, accessController)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) SetConfig(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "setConfig", signers, f, recipientAddressesAndWeights)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SetConfig(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfig(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfig(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfig(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) SetConfigActive(opts *bind.TransactOpts, donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "setConfigActive", donConfigIndex, isActive)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SetConfigActive(donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfigActive(&_DestinationVerifier.TransactOpts, donConfigIndex, isActive)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfigActive(donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfigActive(&_DestinationVerifier.TransactOpts, donConfigIndex, isActive)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) SetConfigWithActivationTime(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "setConfigWithActivationTime", signers, f, recipientAddressesAndWeights, activationTime)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SetConfigWithActivationTime(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfigWithActivationTime(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights, activationTime)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfigWithActivationTime(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetConfigWithActivationTime(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights, activationTime)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) SetFeeManager(opts *bind.TransactOpts, feeManager common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "setFeeManager", feeManager)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) SetFeeManager(feeManager common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetFeeManager(&_DestinationVerifier.TransactOpts, feeManager)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) SetFeeManager(feeManager common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.SetFeeManager(&_DestinationVerifier.TransactOpts, feeManager)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "transferOwnership", to)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.TransferOwnership(&_DestinationVerifier.TransactOpts, to)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.TransferOwnership(&_DestinationVerifier.TransactOpts, to)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) Verify(opts *bind.TransactOpts, signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "verify", signedReport, parameterPayload, sender)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) Verify(signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.Verify(&_DestinationVerifier.TransactOpts, signedReport, parameterPayload, sender)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) Verify(signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.Verify(&_DestinationVerifier.TransactOpts, signedReport, parameterPayload, sender)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactor) VerifyBulk(opts *bind.TransactOpts, signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.contract.Transact(opts, "verifyBulk", signedReports, parameterPayload, sender)
+}
+
+func (_DestinationVerifier *DestinationVerifierSession) VerifyBulk(signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.VerifyBulk(&_DestinationVerifier.TransactOpts, signedReports, parameterPayload, sender)
+}
+
+func (_DestinationVerifier *DestinationVerifierTransactorSession) VerifyBulk(signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) {
+	return _DestinationVerifier.Contract.VerifyBulk(&_DestinationVerifier.TransactOpts, signedReports, parameterPayload, sender)
+}
+
+type DestinationVerifierAccessControllerSetIterator struct {
+	Event *DestinationVerifierAccessControllerSet
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierAccessControllerSetIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierAccessControllerSet)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierAccessControllerSet)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierAccessControllerSetIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierAccessControllerSetIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierAccessControllerSet struct {
+	OldAccessController common.Address
+	NewAccessController common.Address
+	Raw                 types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterAccessControllerSet(opts *bind.FilterOpts) (*DestinationVerifierAccessControllerSetIterator, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "AccessControllerSet")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierAccessControllerSetIterator{contract: _DestinationVerifier.contract, event: "AccessControllerSet", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchAccessControllerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierAccessControllerSet) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "AccessControllerSet")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierAccessControllerSet)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "AccessControllerSet", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseAccessControllerSet(log types.Log) (*DestinationVerifierAccessControllerSet, error) {
+	event := new(DestinationVerifierAccessControllerSet)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "AccessControllerSet", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierConfigActivatedIterator struct {
+	Event *DestinationVerifierConfigActivated
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierConfigActivatedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierConfigActivated)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierConfigActivated)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierConfigActivatedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierConfigActivatedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierConfigActivated struct {
+	DonConfigId [24]byte
+	IsActive    bool
+	Raw         types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigActivated(opts *bind.FilterOpts) (*DestinationVerifierConfigActivatedIterator, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigActivated")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierConfigActivatedIterator{contract: _DestinationVerifier.contract, event: "ConfigActivated", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigActivated) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigActivated")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierConfigActivated)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigActivated", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigActivated(log types.Log) (*DestinationVerifierConfigActivated, error) {
+	event := new(DestinationVerifierConfigActivated)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigActivated", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierConfigRemovedIterator struct {
+	Event *DestinationVerifierConfigRemoved
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierConfigRemovedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierConfigRemoved)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierConfigRemoved)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierConfigRemovedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierConfigRemovedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierConfigRemoved struct {
+	DonConfigId [24]byte
+	Raw         types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigRemoved(opts *bind.FilterOpts) (*DestinationVerifierConfigRemovedIterator, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigRemoved")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierConfigRemovedIterator{contract: _DestinationVerifier.contract, event: "ConfigRemoved", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigRemoved(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigRemoved) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigRemoved")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierConfigRemoved)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigRemoved", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigRemoved(log types.Log) (*DestinationVerifierConfigRemoved, error) {
+	event := new(DestinationVerifierConfigRemoved)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigRemoved", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierConfigSetIterator struct {
+	Event *DestinationVerifierConfigSet
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierConfigSetIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierConfigSet)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierConfigSet)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierConfigSetIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierConfigSetIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierConfigSet struct {
+	DonConfigId                  [24]byte
+	Signers                      []common.Address
+	F                            uint8
+	RecipientAddressesAndWeights []CommonAddressAndWeight
+	Raw                          types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigSet(opts *bind.FilterOpts, donConfigId [][24]byte) (*DestinationVerifierConfigSetIterator, error) {
+
+	var donConfigIdRule []interface{}
+	for _, donConfigIdItem := range donConfigId {
+		donConfigIdRule = append(donConfigIdRule, donConfigIdItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigSet", donConfigIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierConfigSetIterator{contract: _DestinationVerifier.contract, event: "ConfigSet", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigSet, donConfigId [][24]byte) (event.Subscription, error) {
+
+	var donConfigIdRule []interface{}
+	for _, donConfigIdItem := range donConfigId {
+		donConfigIdRule = append(donConfigIdRule, donConfigIdItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigSet", donConfigIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierConfigSet)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigSet", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigSet(log types.Log) (*DestinationVerifierConfigSet, error) {
+	event := new(DestinationVerifierConfigSet)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigSet", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierFeeManagerSetIterator struct {
+	Event *DestinationVerifierFeeManagerSet
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierFeeManagerSetIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierFeeManagerSet)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierFeeManagerSet)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierFeeManagerSetIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierFeeManagerSetIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierFeeManagerSet struct {
+	OldFeeManager common.Address
+	NewFeeManager common.Address
+	Raw           types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterFeeManagerSet(opts *bind.FilterOpts) (*DestinationVerifierFeeManagerSetIterator, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "FeeManagerSet")
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierFeeManagerSetIterator{contract: _DestinationVerifier.contract, event: "FeeManagerSet", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchFeeManagerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierFeeManagerSet) (event.Subscription, error) {
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "FeeManagerSet")
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierFeeManagerSet)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "FeeManagerSet", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseFeeManagerSet(log types.Log) (*DestinationVerifierFeeManagerSet, error) {
+	event := new(DestinationVerifierFeeManagerSet)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "FeeManagerSet", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierOwnershipTransferRequestedIterator struct {
+	Event *DestinationVerifierOwnershipTransferRequested
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierOwnershipTransferRequestedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierOwnershipTransferRequested)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierOwnershipTransferRequested)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierOwnershipTransferRequestedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierOwnershipTransferRequestedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierOwnershipTransferRequested struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferRequestedIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierOwnershipTransferRequestedIterator{contract: _DestinationVerifier.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierOwnershipTransferRequested)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierOwnershipTransferRequested, error) {
+	event := new(DestinationVerifierOwnershipTransferRequested)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierOwnershipTransferredIterator struct {
+	Event *DestinationVerifierOwnershipTransferred
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierOwnershipTransferredIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierOwnershipTransferred)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierOwnershipTransferred)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierOwnershipTransferredIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierOwnershipTransferredIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierOwnershipTransferred struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferredIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierOwnershipTransferredIterator{contract: _DestinationVerifier.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierOwnershipTransferred)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationVerifierOwnershipTransferred, error) {
+	event := new(DestinationVerifierOwnershipTransferred)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierReportVerifiedIterator struct {
+	Event *DestinationVerifierReportVerified
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierReportVerifiedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierReportVerified)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierReportVerified)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierReportVerifiedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierReportVerifiedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierReportVerified struct {
+	FeedId    [32]byte
+	Requester common.Address
+	Raw       types.Log
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*DestinationVerifierReportVerifiedIterator, error) {
+
+	var feedIdRule []interface{}
+	for _, feedIdItem := range feedId {
+		feedIdRule = append(feedIdRule, feedIdItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ReportVerified", feedIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierReportVerifiedIterator{contract: _DestinationVerifier.contract, event: "ReportVerified", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) WatchReportVerified(opts *bind.WatchOpts, sink chan<- *DestinationVerifierReportVerified, feedId [][32]byte) (event.Subscription, error) {
+
+	var feedIdRule []interface{}
+	for _, feedIdItem := range feedId {
+		feedIdRule = append(feedIdRule, feedIdItem)
+	}
+
+	logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ReportVerified", feedIdRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierReportVerified)
+				if err := _DestinationVerifier.contract.UnpackLog(event, "ReportVerified", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifier *DestinationVerifierFilterer) ParseReportVerified(log types.Log) (*DestinationVerifierReportVerified, error) {
+	event := new(DestinationVerifierReportVerified)
+	if err := _DestinationVerifier.contract.UnpackLog(event, "ReportVerified", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+func (_DestinationVerifier *DestinationVerifier) ParseLog(log types.Log) (generated.AbigenLog, error) {
+	switch log.Topics[0] {
+	case _DestinationVerifier.abi.Events["AccessControllerSet"].ID:
+		return _DestinationVerifier.ParseAccessControllerSet(log)
+	case _DestinationVerifier.abi.Events["ConfigActivated"].ID:
+		return _DestinationVerifier.ParseConfigActivated(log)
+	case _DestinationVerifier.abi.Events["ConfigRemoved"].ID:
+		return _DestinationVerifier.ParseConfigRemoved(log)
+	case _DestinationVerifier.abi.Events["ConfigSet"].ID:
+		return _DestinationVerifier.ParseConfigSet(log)
+	case _DestinationVerifier.abi.Events["FeeManagerSet"].ID:
+		return _DestinationVerifier.ParseFeeManagerSet(log)
+	case _DestinationVerifier.abi.Events["OwnershipTransferRequested"].ID:
+		return _DestinationVerifier.ParseOwnershipTransferRequested(log)
+	case _DestinationVerifier.abi.Events["OwnershipTransferred"].ID:
+		return _DestinationVerifier.ParseOwnershipTransferred(log)
+	case _DestinationVerifier.abi.Events["ReportVerified"].ID:
+		return _DestinationVerifier.ParseReportVerified(log)
+
+	default:
+		return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0])
+	}
+}
+
+func (DestinationVerifierAccessControllerSet) Topic() common.Hash {
+	return common.HexToHash("0x953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b6")
+}
+
+func (DestinationVerifierConfigActivated) Topic() common.Hash {
+	return common.HexToHash("0x90186a1e77b498ec417ea88bd026cae00d7043c357cc45221777623bda582dd4")
+}
+
+func (DestinationVerifierConfigRemoved) Topic() common.Hash {
+	return common.HexToHash("0x970fd8f3ebdd9a271080aacf9807a5c709be0b448e4047a6fc212b8cc165368d")
+}
+
+func (DestinationVerifierConfigSet) Topic() common.Hash {
+	return common.HexToHash("0x2d763a674a99583454a287d792819ffb9ff7e791c23e7745a082701136ce336c")
+}
+
+func (DestinationVerifierFeeManagerSet) Topic() common.Hash {
+	return common.HexToHash("0x04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f03")
+}
+
+func (DestinationVerifierOwnershipTransferRequested) Topic() common.Hash {
+	return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278")
+}
+
+func (DestinationVerifierOwnershipTransferred) Topic() common.Hash {
+	return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0")
+}
+
+func (DestinationVerifierReportVerified) Topic() common.Hash {
+	return common.HexToHash("0x58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc5")
+}
+
+func (_DestinationVerifier *DestinationVerifier) Address() common.Address {
+	return _DestinationVerifier.address
+}
+
+type DestinationVerifierInterface interface {
+	IVerifierProxy(opts *bind.CallOpts) (common.Address, error)
+
+	Owner(opts *bind.CallOpts) (common.Address, error)
+
+	SAccessController(opts *bind.CallOpts) (common.Address, error)
+
+	SFeeManager(opts *bind.CallOpts) (common.Address, error)
+
+	SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error)
+
+	TypeAndVersion(opts *bind.CallOpts) (string, error)
+
+	AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error)
+
+	RemoveLatestConfig(opts *bind.TransactOpts) (*types.Transaction, error)
+
+	SetAccessController(opts *bind.TransactOpts, accessController common.Address) (*types.Transaction, error)
+
+	SetConfig(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error)
+
+	SetConfigActive(opts *bind.TransactOpts, donConfigIndex *big.Int, isActive bool) (*types.Transaction, error)
+
+	SetConfigWithActivationTime(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error)
+
+	SetFeeManager(opts *bind.TransactOpts, feeManager common.Address) (*types.Transaction, error)
+
+	TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error)
+
+	Verify(opts *bind.TransactOpts, signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error)
+
+	VerifyBulk(opts *bind.TransactOpts, signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error)
+
+	FilterAccessControllerSet(opts *bind.FilterOpts) (*DestinationVerifierAccessControllerSetIterator, error)
+
+	WatchAccessControllerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierAccessControllerSet) (event.Subscription, error)
+
+	ParseAccessControllerSet(log types.Log) (*DestinationVerifierAccessControllerSet, error)
+
+	FilterConfigActivated(opts *bind.FilterOpts) (*DestinationVerifierConfigActivatedIterator, error)
+
+	WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigActivated) (event.Subscription, error)
+
+	ParseConfigActivated(log types.Log) (*DestinationVerifierConfigActivated, error)
+
+	FilterConfigRemoved(opts *bind.FilterOpts) (*DestinationVerifierConfigRemovedIterator, error)
+
+	WatchConfigRemoved(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigRemoved) (event.Subscription, error)
+
+	ParseConfigRemoved(log types.Log) (*DestinationVerifierConfigRemoved, error)
+
+	FilterConfigSet(opts *bind.FilterOpts, donConfigId [][24]byte) (*DestinationVerifierConfigSetIterator, error)
+
+	WatchConfigSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigSet, donConfigId [][24]byte) (event.Subscription, error)
+
+	ParseConfigSet(log types.Log) (*DestinationVerifierConfigSet, error)
+
+	FilterFeeManagerSet(opts *bind.FilterOpts) (*DestinationVerifierFeeManagerSetIterator, error)
+
+	WatchFeeManagerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierFeeManagerSet) (event.Subscription, error)
+
+	ParseFeeManagerSet(log types.Log) (*DestinationVerifierFeeManagerSet, error)
+
+	FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferRequestedIterator, error)
+
+	WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierOwnershipTransferRequested, error)
+
+	FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferredIterator, error)
+
+	WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferred(log types.Log) (*DestinationVerifierOwnershipTransferred, error)
+
+	FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*DestinationVerifierReportVerifiedIterator, error)
+
+	WatchReportVerified(opts *bind.WatchOpts, sink chan<- *DestinationVerifierReportVerified, feedId [][32]byte) (event.Subscription, error)
+
+	ParseReportVerified(log types.Log) (*DestinationVerifierReportVerified, error)
+
+	ParseLog(log types.Log) (generated.AbigenLog, error)
+
+	Address() common.Address
+}
diff --git a/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go b/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go
new file mode 100644
index 00000000000..a2ae546d9bb
--- /dev/null
+++ b/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go
@@ -0,0 +1,676 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package destination_verifier_proxy
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"strings"
+
+	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated"
+)
+
+var (
+	_ = errors.New
+	_ = big.NewInt
+	_ = strings.NewReader
+	_ = ethereum.NotFound
+	_ = bind.Bind
+	_ = common.Big1
+	_ = types.BloomLookup
+	_ = event.NewSubscription
+	_ = abi.ConvertType
+)
+
+var DestinationVerifierProxyMetaData = &bind.MetaData{
+	ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"VerifierInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_accessController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_feeManager\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"setVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"payloads\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"}],\"name\":\"verifyBulk\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"verifiedReports\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"}]",
+	Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61130b806101576000396000f3fe6080604052600436106100b15760003560e01c80638da5cb5b11610069578063f2fde38b1161004e578063f2fde38b146101eb578063f7e83aee1461020b578063f873a61c1461021e57600080fd5b80638da5cb5b146101ab57806394ba2846146101d657600080fd5b806338416b5b1161009a57806338416b5b1461013a5780635437988d1461017457806379ba50971461019657600080fd5b806301ffc9a7146100b6578063181f5a77146100eb575b600080fd5b3480156100c257600080fd5b506100d66100d1366004610c56565b61023e565b60405190151581526020015b60405180910390f35b3480156100f757600080fd5b5060408051808201909152601e81527f44657374696e6174696f6e566572696669657250726f787920312e302e30000060208201525b6040516100e29190610d0d565b34801561014657600080fd5b5061014f6103bb565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e2565b34801561018057600080fd5b5061019461018f366004610d42565b610454565b005b3480156101a257600080fd5b506101946107c8565b3480156101b757600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661014f565b3480156101e257600080fd5b5061014f6108c5565b3480156101f757600080fd5b50610194610206366004610d42565b610935565b61012d610219366004610da8565b610949565b61023161022c366004610e14565b610a18565b6040516100e29190610e95565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f5437988d0000000000000000000000000000000000000000000000000000000014806102d157507fffffffff0000000000000000000000000000000000000000000000000000000082167ff7e83aee00000000000000000000000000000000000000000000000000000000145b8061031d57507fffffffff0000000000000000000000000000000000000000000000000000000082167ff873a61c00000000000000000000000000000000000000000000000000000000145b8061036957507fffffffff0000000000000000000000000000000000000000000000000000000082167f38416b5b00000000000000000000000000000000000000000000000000000000145b806103b557507fffffffff0000000000000000000000000000000000000000000000000000000082167f94ba284600000000000000000000000000000000000000000000000000000000145b92915050565b600254604080517f38416b5b000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff16916338416b5b9160048083019260209291908290030181865afa15801561042b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044f9190610f15565b905090565b61045c610ade565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f94ba284600000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa1580156104e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050a9190610f32565b15806105c157506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f38416b5b00000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa15801561059b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105bf9190610f32565b155b8061067757506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f294d2bb100000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610651573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106759190610f32565b155b8061072d57506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527fd7c72e4e00000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072b9190610f32565b155b15610781576040517f96ac86f300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024015b60405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60015473ffffffffffffffffffffffffffffffffffffffff163314610849576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610778565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b600254604080517f94ba2846000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff16916394ba28469160048083019260209291908290030181865afa15801561042b573d6000803e3d6000fd5b61093d610ade565b61094681610b61565b50565b6002546040517f294d2bb100000000000000000000000000000000000000000000000000000000815260609173ffffffffffffffffffffffffffffffffffffffff169063294d2bb19034906109aa9089908990899089903390600401610f9d565b60006040518083038185885af11580156109c8573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a0f91908101906110f5565b95945050505050565b6002546040517fd7c72e4e00000000000000000000000000000000000000000000000000000000815260609173ffffffffffffffffffffffffffffffffffffffff169063d7c72e4e903490610a79908990899089908990339060040161112a565b60006040518083038185885af1158015610a97573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a0f919081019061123b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610b5f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610778565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610be0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610778565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215610c6857600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610c9857600080fd5b9392505050565b60005b83811015610cba578181015183820152602001610ca2565b50506000910152565b60008151808452610cdb816020860160208601610c9f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c986020830184610cc3565b73ffffffffffffffffffffffffffffffffffffffff8116811461094657600080fd5b600060208284031215610d5457600080fd5b8135610c9881610d20565b60008083601f840112610d7157600080fd5b50813567ffffffffffffffff811115610d8957600080fd5b602083019150836020828501011115610da157600080fd5b9250929050565b60008060008060408587031215610dbe57600080fd5b843567ffffffffffffffff80821115610dd657600080fd5b610de288838901610d5f565b90965094506020870135915080821115610dfb57600080fd5b50610e0887828801610d5f565b95989497509550505050565b60008060008060408587031215610e2a57600080fd5b843567ffffffffffffffff80821115610e4257600080fd5b818701915087601f830112610e5657600080fd5b813581811115610e6557600080fd5b8860208260051b8501011115610e7a57600080fd5b602092830196509450908601359080821115610dfb57600080fd5b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015610f08577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452610ef6858351610cc3565b94509285019290850190600101610ebc565b5092979650505050505050565b600060208284031215610f2757600080fd5b8151610c9881610d20565b600060208284031215610f4457600080fd5b81518015158114610c9857600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b606081526000610fb1606083018789610f54565b8281036020840152610fc4818688610f54565b91505073ffffffffffffffffffffffffffffffffffffffff831660408301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561106357611063610fed565b604052919050565b600082601f83011261107c57600080fd5b815167ffffffffffffffff81111561109657611096610fed565b6110c760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161101c565b8181528460208386010111156110dc57600080fd5b6110ed826020830160208701610c9f565b949350505050565b60006020828403121561110757600080fd5b815167ffffffffffffffff81111561111e57600080fd5b6110ed8482850161106b565b6060808252810185905260006080600587901b8301810190830188835b898110156111f6577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8086850301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18c36030181126111a857600080fd5b8b01602081810191359067ffffffffffffffff8211156111c757600080fd5b8136038313156111d657600080fd5b6111e1878385610f54565b96509485019493909301925050600101611147565b505050828103602084015261120c818688610f54565b915050611231604083018473ffffffffffffffffffffffffffffffffffffffff169052565b9695505050505050565b6000602080838503121561124e57600080fd5b825167ffffffffffffffff8082111561126657600080fd5b818501915085601f83011261127a57600080fd5b81518181111561128c5761128c610fed565b8060051b61129b85820161101c565b91825283810185019185810190898411156112b557600080fd5b86860192505b838310156112f1578251858111156112d35760008081fd5b6112e18b89838a010161106b565b83525091860191908601906112bb565b999850505050505050505056fea164736f6c6343000813000a",
+}
+
+var DestinationVerifierProxyABI = DestinationVerifierProxyMetaData.ABI
+
+var DestinationVerifierProxyBin = DestinationVerifierProxyMetaData.Bin
+
+func DeployDestinationVerifierProxy(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *DestinationVerifierProxy, error) {
+	parsed, err := DestinationVerifierProxyMetaData.GetAbi()
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	if parsed == nil {
+		return common.Address{}, nil, nil, errors.New("GetABI returned nil")
+	}
+
+	address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(DestinationVerifierProxyBin), backend)
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	return address, tx, &DestinationVerifierProxy{address: address, abi: *parsed, DestinationVerifierProxyCaller: DestinationVerifierProxyCaller{contract: contract}, DestinationVerifierProxyTransactor: DestinationVerifierProxyTransactor{contract: contract}, DestinationVerifierProxyFilterer: DestinationVerifierProxyFilterer{contract: contract}}, nil
+}
+
+type DestinationVerifierProxy struct {
+	address common.Address
+	abi     abi.ABI
+	DestinationVerifierProxyCaller
+	DestinationVerifierProxyTransactor
+	DestinationVerifierProxyFilterer
+}
+
+type DestinationVerifierProxyCaller struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierProxyTransactor struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierProxyFilterer struct {
+	contract *bind.BoundContract
+}
+
+type DestinationVerifierProxySession struct {
+	Contract     *DestinationVerifierProxy
+	CallOpts     bind.CallOpts
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationVerifierProxyCallerSession struct {
+	Contract *DestinationVerifierProxyCaller
+	CallOpts bind.CallOpts
+}
+
+type DestinationVerifierProxyTransactorSession struct {
+	Contract     *DestinationVerifierProxyTransactor
+	TransactOpts bind.TransactOpts
+}
+
+type DestinationVerifierProxyRaw struct {
+	Contract *DestinationVerifierProxy
+}
+
+type DestinationVerifierProxyCallerRaw struct {
+	Contract *DestinationVerifierProxyCaller
+}
+
+type DestinationVerifierProxyTransactorRaw struct {
+	Contract *DestinationVerifierProxyTransactor
+}
+
+func NewDestinationVerifierProxy(address common.Address, backend bind.ContractBackend) (*DestinationVerifierProxy, error) {
+	abi, err := abi.JSON(strings.NewReader(DestinationVerifierProxyABI))
+	if err != nil {
+		return nil, err
+	}
+	contract, err := bindDestinationVerifierProxy(address, backend, backend, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxy{address: address, abi: abi, DestinationVerifierProxyCaller: DestinationVerifierProxyCaller{contract: contract}, DestinationVerifierProxyTransactor: DestinationVerifierProxyTransactor{contract: contract}, DestinationVerifierProxyFilterer: DestinationVerifierProxyFilterer{contract: contract}}, nil
+}
+
+func NewDestinationVerifierProxyCaller(address common.Address, caller bind.ContractCaller) (*DestinationVerifierProxyCaller, error) {
+	contract, err := bindDestinationVerifierProxy(address, caller, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxyCaller{contract: contract}, nil
+}
+
+func NewDestinationVerifierProxyTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationVerifierProxyTransactor, error) {
+	contract, err := bindDestinationVerifierProxy(address, nil, transactor, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxyTransactor{contract: contract}, nil
+}
+
+func NewDestinationVerifierProxyFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationVerifierProxyFilterer, error) {
+	contract, err := bindDestinationVerifierProxy(address, nil, nil, filterer)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxyFilterer{contract: contract}, nil
+}
+
+func bindDestinationVerifierProxy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+	parsed, err := DestinationVerifierProxyMetaData.GetAbi()
+	if err != nil {
+		return nil, err
+	}
+	return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationVerifierProxy.Contract.DestinationVerifierProxyCaller.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.DestinationVerifierProxyTransactor.contract.Transfer(opts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.DestinationVerifierProxyTransactor.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+	return _DestinationVerifierProxy.Contract.contract.Call(opts, result, method, params...)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.contract.Transfer(opts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.contract.Transact(opts, method, params...)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifierProxy.contract.Call(opts, &out, "owner")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) Owner() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.Owner(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) Owner() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.Owner(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SAccessController(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifierProxy.contract.Call(opts, &out, "s_accessController")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) SAccessController() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.SAccessController(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SAccessController() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.SAccessController(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SFeeManager(opts *bind.CallOpts) (common.Address, error) {
+	var out []interface{}
+	err := _DestinationVerifierProxy.contract.Call(opts, &out, "s_feeManager")
+
+	if err != nil {
+		return *new(common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) SFeeManager() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.SFeeManager(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SFeeManager() (common.Address, error) {
+	return _DestinationVerifierProxy.Contract.SFeeManager(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) {
+	var out []interface{}
+	err := _DestinationVerifierProxy.contract.Call(opts, &out, "supportsInterface", interfaceId)
+
+	if err != nil {
+		return *new(bool), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationVerifierProxy.Contract.SupportsInterface(&_DestinationVerifierProxy.CallOpts, interfaceId)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) {
+	return _DestinationVerifierProxy.Contract.SupportsInterface(&_DestinationVerifierProxy.CallOpts, interfaceId)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) {
+	var out []interface{}
+	err := _DestinationVerifierProxy.contract.Call(opts, &out, "typeAndVersion")
+
+	if err != nil {
+		return *new(string), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+	return out0, err
+
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) TypeAndVersion() (string, error) {
+	return _DestinationVerifierProxy.Contract.TypeAndVersion(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) TypeAndVersion() (string, error) {
+	return _DestinationVerifierProxy.Contract.TypeAndVersion(&_DestinationVerifierProxy.CallOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.contract.Transact(opts, "acceptOwnership")
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.AcceptOwnership(&_DestinationVerifierProxy.TransactOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) AcceptOwnership() (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.AcceptOwnership(&_DestinationVerifierProxy.TransactOpts)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) SetVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.contract.Transact(opts, "setVerifier", verifierAddress)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) SetVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.SetVerifier(&_DestinationVerifierProxy.TransactOpts, verifierAddress)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) SetVerifier(verifierAddress common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.SetVerifier(&_DestinationVerifierProxy.TransactOpts, verifierAddress)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.contract.Transact(opts, "transferOwnership", to)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.TransferOwnership(&_DestinationVerifierProxy.TransactOpts, to)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.TransferOwnership(&_DestinationVerifierProxy.TransactOpts, to)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) Verify(opts *bind.TransactOpts, payload []byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.contract.Transact(opts, "verify", payload, parameterPayload)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) Verify(payload []byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.Verify(&_DestinationVerifierProxy.TransactOpts, payload, parameterPayload)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) Verify(payload []byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.Verify(&_DestinationVerifierProxy.TransactOpts, payload, parameterPayload)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) VerifyBulk(opts *bind.TransactOpts, payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.contract.Transact(opts, "verifyBulk", payloads, parameterPayload)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxySession) VerifyBulk(payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.VerifyBulk(&_DestinationVerifierProxy.TransactOpts, payloads, parameterPayload)
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) VerifyBulk(payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) {
+	return _DestinationVerifierProxy.Contract.VerifyBulk(&_DestinationVerifierProxy.TransactOpts, payloads, parameterPayload)
+}
+
+type DestinationVerifierProxyOwnershipTransferRequestedIterator struct {
+	Event *DestinationVerifierProxyOwnershipTransferRequested
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferRequestedIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierProxyOwnershipTransferRequested)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierProxyOwnershipTransferRequested)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferRequestedIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferRequestedIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierProxyOwnershipTransferRequested struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferRequestedIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifierProxy.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxyOwnershipTransferRequestedIterator{contract: _DestinationVerifierProxy.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifierProxy.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierProxyOwnershipTransferRequested)
+				if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierProxyOwnershipTransferRequested, error) {
+	event := new(DestinationVerifierProxyOwnershipTransferRequested)
+	if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+type DestinationVerifierProxyOwnershipTransferredIterator struct {
+	Event *DestinationVerifierProxyOwnershipTransferred
+
+	contract *bind.BoundContract
+	event    string
+
+	logs chan types.Log
+	sub  ethereum.Subscription
+	done bool
+	fail error
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferredIterator) Next() bool {
+
+	if it.fail != nil {
+		return false
+	}
+
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(DestinationVerifierProxyOwnershipTransferred)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+
+	select {
+	case log := <-it.logs:
+		it.Event = new(DestinationVerifierProxyOwnershipTransferred)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferredIterator) Error() error {
+	return it.fail
+}
+
+func (it *DestinationVerifierProxyOwnershipTransferredIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+type DestinationVerifierProxyOwnershipTransferred struct {
+	From common.Address
+	To   common.Address
+	Raw  types.Log
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferredIterator, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifierProxy.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return &DestinationVerifierProxyOwnershipTransferredIterator{contract: _DestinationVerifierProxy.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+	var fromRule []interface{}
+	for _, fromItem := range from {
+		fromRule = append(fromRule, fromItem)
+	}
+	var toRule []interface{}
+	for _, toItem := range to {
+		toRule = append(toRule, toItem)
+	}
+
+	logs, sub, err := _DestinationVerifierProxy.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+
+				event := new(DestinationVerifierProxyOwnershipTransferred)
+				if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationVerifierProxyOwnershipTransferred, error) {
+	event := new(DestinationVerifierProxyOwnershipTransferred)
+	if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
+		return nil, err
+	}
+	event.Raw = log
+	return event, nil
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxy) ParseLog(log types.Log) (generated.AbigenLog, error) {
+	switch log.Topics[0] {
+	case _DestinationVerifierProxy.abi.Events["OwnershipTransferRequested"].ID:
+		return _DestinationVerifierProxy.ParseOwnershipTransferRequested(log)
+	case _DestinationVerifierProxy.abi.Events["OwnershipTransferred"].ID:
+		return _DestinationVerifierProxy.ParseOwnershipTransferred(log)
+
+	default:
+		return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0])
+	}
+}
+
+func (DestinationVerifierProxyOwnershipTransferRequested) Topic() common.Hash {
+	return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278")
+}
+
+func (DestinationVerifierProxyOwnershipTransferred) Topic() common.Hash {
+	return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0")
+}
+
+func (_DestinationVerifierProxy *DestinationVerifierProxy) Address() common.Address {
+	return _DestinationVerifierProxy.address
+}
+
+type DestinationVerifierProxyInterface interface {
+	Owner(opts *bind.CallOpts) (common.Address, error)
+
+	SAccessController(opts *bind.CallOpts) (common.Address, error)
+
+	SFeeManager(opts *bind.CallOpts) (common.Address, error)
+
+	SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error)
+
+	TypeAndVersion(opts *bind.CallOpts) (string, error)
+
+	AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error)
+
+	SetVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error)
+
+	TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error)
+
+	Verify(opts *bind.TransactOpts, payload []byte, parameterPayload []byte) (*types.Transaction, error)
+
+	VerifyBulk(opts *bind.TransactOpts, payloads [][]byte, parameterPayload []byte) (*types.Transaction, error)
+
+	FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferRequestedIterator, error)
+
+	WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierProxyOwnershipTransferRequested, error)
+
+	FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferredIterator, error)
+
+	WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error)
+
+	ParseOwnershipTransferred(log types.Log) (*DestinationVerifierProxyOwnershipTransferred, error)
+
+	ParseLog(log types.Log) (generated.AbigenLog, error)
+
+	Address() common.Address
+}
diff --git a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go
index 4d140ea064a..f834686dfef 100644
--- a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go
+++ b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go
@@ -34,8 +34,8 @@ type CommonAddressAndWeight struct {
 }
 
 var ErroredVerifierMetaData = &bind.MetaData{
-	ABI: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfigFromSource\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]",
-	Bin: "0x608060405234801561001057600080fd5b50610c2e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063b70d929d11610076578063e7db9c2a1161005b578063e7db9c2a146101d1578063e84f128e146101e4578063f01072211461021a57600080fd5b8063b70d929d14610188578063ded6307c146101be57600080fd5b80633dd86430116100a75780633dd864301461014d578063564a0a7a1461016257806394d959801461017557600080fd5b806301ffc9a7146100c35780633d3ac1b51461012d575b600080fd5b6101186100d136600461059a565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61014061013b366004610741565b610228565b604051610124919061078f565b61016061015b3660046107fb565b610292565b005b6101606101703660046107fb565b6102f4565b610160610183366004610814565b610356565b61019b6101963660046107fb565b6103b8565b604080519315158452602084019290925263ffffffff1690820152606001610124565b6101606101cc366004610814565b610447565b6101606101df3660046109f1565b6104a9565b6101f76101f23660046107fb565b61050b565b6040805163ffffffff948516815293909216602084015290820152606001610124565b6101606101df366004610b24565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4661696c656420746f207665726966790000000000000000000000000000000060448201526060906064015b60405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4661696c656420746f20616374697661746520666565640000000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4661696c656420746f20646561637469766174652066656564000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4661696c656420746f206465616374697661746520636f6e66696700000000006044820152606401610289565b60008060006040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610289906020808252602c908201527f4661696c656420746f20676574206c617465737420636f6e666967206469676560408201527f737420616e642065706f63680000000000000000000000000000000000000000606082015260800190565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4661696c656420746f20616374697661746520636f6e666967000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4661696c656420746f2073657420636f6e6669670000000000000000000000006044820152606401610289565b60008060006040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102899060208082526023908201527f4661696c656420746f20676574206c617465737420636f6e666967206465746160408201527f696c730000000000000000000000000000000000000000000000000000000000606082015260800190565b6000602082840312156105ac57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146105dc57600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715610635576106356105e3565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610682576106826105e3565b604052919050565b600082601f83011261069b57600080fd5b813567ffffffffffffffff8111156106b5576106b56105e3565b6106e660207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161063b565b8181528460208386010111156106fb57600080fd5b816020850160208301376000918101602001919091529392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461073c57600080fd5b919050565b6000806040838503121561075457600080fd5b823567ffffffffffffffff81111561076b57600080fd5b6107778582860161068a565b92505061078660208401610718565b90509250929050565b600060208083528351808285015260005b818110156107bc578581018301518582016040015282016107a0565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60006020828403121561080d57600080fd5b5035919050565b6000806040838503121561082757600080fd5b50508035926020909101359150565b803563ffffffff8116811461073c57600080fd5b600067ffffffffffffffff821115610864576108646105e3565b5060051b60200190565b600082601f83011261087f57600080fd5b8135602061089461088f8361084a565b61063b565b82815260059290921b840181019181810190868411156108b357600080fd5b8286015b848110156108d5576108c881610718565b83529183019183016108b7565b509695505050505050565b600082601f8301126108f157600080fd5b8135602061090161088f8361084a565b82815260059290921b8401810191818101908684111561092057600080fd5b8286015b848110156108d55780358352918301918301610924565b803560ff8116811461073c57600080fd5b803567ffffffffffffffff8116811461073c57600080fd5b600082601f83011261097557600080fd5b8135602061098561088f8361084a565b82815260069290921b840181019181810190868411156109a457600080fd5b8286015b848110156108d557604081890312156109c15760008081fd5b6109c9610612565b6109d282610718565b81526109df85830161094c565b818601528352918301916040016109a8565b60008060008060008060008060008060006101608c8e031215610a1357600080fd5b8b359a5060208c01359950610a2a60408d01610718565b9850610a3860608d01610836565b975067ffffffffffffffff8060808e01351115610a5457600080fd5b610a648e60808f01358f0161086e565b97508060a08e01351115610a7757600080fd5b610a878e60a08f01358f016108e0565b9650610a9560c08e0161093b565b95508060e08e01351115610aa857600080fd5b610ab88e60e08f01358f0161068a565b9450610ac76101008e0161094c565b9350806101208e01351115610adb57600080fd5b610aec8e6101208f01358f0161068a565b9250806101408e01351115610b0057600080fd5b50610b128d6101408e01358e01610964565b90509295989b509295989b9093969950565b600080600080600080600080610100898b031215610b4157600080fd5b88359750602089013567ffffffffffffffff80821115610b6057600080fd5b610b6c8c838d0161086e565b985060408b0135915080821115610b8257600080fd5b610b8e8c838d016108e0565b9750610b9c60608c0161093b565b965060808b0135915080821115610bb257600080fd5b610bbe8c838d0161068a565b9550610bcc60a08c0161094c565b945060c08b0135915080821115610be257600080fd5b610bee8c838d0161068a565b935060e08b0135915080821115610c0457600080fd5b50610c118b828c01610964565b915050929598509295989093965056fea164736f6c6343000813000a",
+	ABI: "[{\"inputs\":[],\"name\":\"FailedToActivateConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToActivateFeed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToDeactivateConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToDeactivateFeed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToGetLatestConfigDetails\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToGetLatestConfigDigestAndEpoch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToSetConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToVerify\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfigFromSource\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]",
+	Bin: "0x608060405234801561001057600080fd5b50610a58806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063b70d929d11610076578063e7db9c2a1161005b578063e7db9c2a146101d1578063e84f128e146101e4578063f01072211461021a57600080fd5b8063b70d929d14610188578063ded6307c146101be57600080fd5b80633dd86430116100a75780633dd864301461014d578063564a0a7a1461016257806394d959801461017557600080fd5b806301ffc9a7146100c35780633d3ac1b51461012d575b600080fd5b6101186100d13660046103c4565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61014061013b36600461056b565b610228565b60405161012491906105b9565b61016061015b366004610625565b61025c565b005b610160610170366004610625565b61028e565b61016061018336600461063e565b6102c0565b61019b610196366004610625565b6102f2565b604080519315158452602084019290925263ffffffff1690820152606001610124565b6101606101cc36600461063e565b610329565b6101606101df36600461081b565b61035b565b6101f76101f2366004610625565b61038d565b6040805163ffffffff948516815293909216602084015290820152606001610124565b6101606101df36600461094e565b60606040517fcf2e344600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f9601b68300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa03564b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f8a406e4600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006040517fbbc0083000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7adb7c9600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f35e91bf100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006040517fa06d64a000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602082840312156103d657600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461040657600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561045f5761045f61040d565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156104ac576104ac61040d565b604052919050565b600082601f8301126104c557600080fd5b813567ffffffffffffffff8111156104df576104df61040d565b61051060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610465565b81815284602083860101111561052557600080fd5b816020850160208301376000918101602001919091529392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461056657600080fd5b919050565b6000806040838503121561057e57600080fd5b823567ffffffffffffffff81111561059557600080fd5b6105a1858286016104b4565b9250506105b060208401610542565b90509250929050565b600060208083528351808285015260005b818110156105e6578581018301518582016040015282016105ca565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60006020828403121561063757600080fd5b5035919050565b6000806040838503121561065157600080fd5b50508035926020909101359150565b803563ffffffff8116811461056657600080fd5b600067ffffffffffffffff82111561068e5761068e61040d565b5060051b60200190565b600082601f8301126106a957600080fd5b813560206106be6106b983610674565b610465565b82815260059290921b840181019181810190868411156106dd57600080fd5b8286015b848110156106ff576106f281610542565b83529183019183016106e1565b509695505050505050565b600082601f83011261071b57600080fd5b8135602061072b6106b983610674565b82815260059290921b8401810191818101908684111561074a57600080fd5b8286015b848110156106ff578035835291830191830161074e565b803560ff8116811461056657600080fd5b803567ffffffffffffffff8116811461056657600080fd5b600082601f83011261079f57600080fd5b813560206107af6106b983610674565b82815260069290921b840181019181810190868411156107ce57600080fd5b8286015b848110156106ff57604081890312156107eb5760008081fd5b6107f361043c565b6107fc82610542565b8152610809858301610776565b818601528352918301916040016107d2565b60008060008060008060008060008060006101608c8e03121561083d57600080fd5b8b359a5060208c0135995061085460408d01610542565b985061086260608d01610660565b975067ffffffffffffffff8060808e0135111561087e57600080fd5b61088e8e60808f01358f01610698565b97508060a08e013511156108a157600080fd5b6108b18e60a08f01358f0161070a565b96506108bf60c08e01610765565b95508060e08e013511156108d257600080fd5b6108e28e60e08f01358f016104b4565b94506108f16101008e01610776565b9350806101208e0135111561090557600080fd5b6109168e6101208f01358f016104b4565b9250806101408e0135111561092a57600080fd5b5061093c8d6101408e01358e0161078e565b90509295989b509295989b9093969950565b600080600080600080600080610100898b03121561096b57600080fd5b88359750602089013567ffffffffffffffff8082111561098a57600080fd5b6109968c838d01610698565b985060408b01359150808211156109ac57600080fd5b6109b88c838d0161070a565b97506109c660608c01610765565b965060808b01359150808211156109dc57600080fd5b6109e88c838d016104b4565b95506109f660a08c01610776565b945060c08b0135915080821115610a0c57600080fd5b610a188c838d016104b4565b935060e08b0135915080821115610a2e57600080fd5b50610a3b8b828c0161078e565b915050929598509295989093965056fea164736f6c6343000813000a",
 }
 
 var ErroredVerifierABI = ErroredVerifierMetaData.ABI
diff --git a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt
index 729d3a295cd..0eec657b4c7 100644
--- a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt
+++ b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt
@@ -2,7 +2,11 @@ GETH_VERSION: 1.13.8
 channel_config_store: ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin c90e29d9f1a885098982b6175e0447416431b28c605273c807694ac7141e9167
 channel_config_verifier_proxy: ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.abi ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.bin 655658e5f61dfadfe3268de04f948b7e690ad03ca45676e645d6cd6018154661
 channel_verifier: ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.abi ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.bin e6020553bd8e3e6b250fcaffe7efd22aea955c8c1a0eb05d282fdeb0ab6550b7
-errored_verifier: ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.bin a3e5a77262e13ee30fe8d35551b32a3452d71929e43fd780bbfefeaf4aa62e43
+destination_fee_manager: ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin c581af84832b8fd886685f59518bcdb11bd1c9b508d88b07c04d6226e6a2789e
+destination_reward_manager: ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin 6aed4313578f74ede71bcb60674391103d265d96d56d4736a79ef4128f0590f4
+destination_verifier: ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.abi ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.bin 2dc118aecd5c30d34a69354a9fb603beb98d46215a18d31c59f0f7902fd8f4c2
+destination_verifier_proxy: ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.abi ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.bin a4bf230bbba8a7b8e32a85a6161ca1343f7472b257c358a73ac37996809ce1c0
+errored_verifier: ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.bin ad8ac8d6b99890081725e2304d79d1ba7dd5212b89d130aa9689f4269eed4691
 exposed_channel_verifier: ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.abi ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.bin c21cde078900241c06de69e2bc5d906c5ef558b52db66caa68bed065940a2253
 exposed_verifier: ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.abi ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.bin 00816ab345f768e522c79abadeadf9155c2c688067e18f8f73e5d6ab71037663
 fee_manager: ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.bin edc85f34294ae7c90d45c4c71eb5c105c60a4842dfbbf700c692870ffcc403a1
diff --git a/core/gethwrappers/llo-feeds/go_generate.go b/core/gethwrappers/llo-feeds/go_generate.go
index 5e5b841b72d..688b503cc1e 100644
--- a/core/gethwrappers/llo-feeds/go_generate.go
+++ b/core/gethwrappers/llo-feeds/go_generate.go
@@ -12,3 +12,8 @@ package gethwrappers
 //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin ChannelConfigStore channel_config_store
 //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.abi ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.bin ChannelVerifier channel_verifier
 //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.abi ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.bin ExposedChannelVerifier exposed_channel_verifier
+
+//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.abi ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.bin DestinationVerifier destination_verifier
+//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.abi ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.bin DestinationVerifierProxy destination_verifier_proxy
+//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin DestinationFeeManager destination_fee_manager
+//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin DestinationRewardManager destination_reward_manager