diff --git a/Clarinet.toml b/Clarinet.toml
index 9d1ea6ee..8fd0eb48 100644
--- a/Clarinet.toml
+++ b/Clarinet.toml
@@ -153,6 +153,11 @@ path = "contracts/proposals/ccip014-pox-3-v2.clar"
 clarity_version = 2
 epoch = 2.4
 
+[contracts.ccip017]
+path = "contracts/proposals/ccip017-extend-direct-execute-sunset-period.clar"
+clarity_version = 2
+epoch = 2.4
+
 # CITYCOINS PROTOCOL TRAITS
 
 [contracts.extension-trait]
diff --git a/contracts/proposals/ccip017-extend-direct-execute-sunset-period.clar b/contracts/proposals/ccip017-extend-direct-execute-sunset-period.clar
new file mode 100644
index 00000000..b229c64f
--- /dev/null
+++ b/contracts/proposals/ccip017-extend-direct-execute-sunset-period.clar
@@ -0,0 +1,265 @@
+;; TRAITS
+
+(impl-trait .proposal-trait.proposal-trait)
+(impl-trait .ccip-015-trait.ccip-015-trait)
+
+;; ERRORS
+
+(define-constant ERR_PANIC (err u1700))
+(define-constant ERR_VOTED_ALREADY (err u1701))
+(define-constant ERR_NOTHING_STACKED (err u1702))
+(define-constant ERR_USER_NOT_FOUND (err u1703))
+(define-constant ERR_PROPOSAL_NOT_ACTIVE (err u1704))
+(define-constant ERR_PROPOSAL_STILL_ACTIVE (err u1705))
+(define-constant ERR_NO_CITY_ID (err u1706))
+(define-constant ERR_VOTE_FAILED (err u1707))
+
+;; CONSTANTS
+
+(define-constant SELF (as-contract tx-sender))
+(define-constant CCIP_017 {
+  name: "Extend Direct Execute Sunset Period",
+  link: "https://github.com/citycoins/governance/blob/feat/add-ccip-017/ccips/ccip-017/ccip-017-extend-direct-execute-sunset-period.md",
+  hash: "7ddbf6152790a730faa059b564a8524abc3c70d3",
+})
+(define-constant SUNSET_BLOCK u147828)
+
+(define-constant VOTE_SCALE_FACTOR (pow u10 u16)) ;; 16 decimal places
+(define-constant MIA_SCALE_BASE (pow u10 u4)) ;; 4 decimal places
+(define-constant MIA_SCALE_FACTOR u8823) ;; 0.8823 or 88.23%
+;; MIA votes scaled to make 1 MIA = 1 NYC
+;; full calculation available in CCIP-017
+
+;; DATA VARS
+
+;; vote block heights
+(define-data-var voteActive bool true)
+(define-data-var voteStart uint u0)
+(define-data-var voteEnd uint u0)
+
+(var-set voteStart block-height)
+
+;; vote tracking
+(define-data-var yesVotes uint u0)
+(define-data-var yesTotal uint u0)
+(define-data-var noVotes uint u0)
+(define-data-var noTotal uint u0)
+
+;; DATA MAPS
+
+(define-map UserVotes
+  uint ;; user ID
+  { ;; vote
+    vote: bool,
+    mia: uint,
+    nyc: uint,
+    total: uint,
+  }
+)
+
+;; PUBLIC FUNCTIONS
+
+(define-public (execute (sender principal))
+  (begin
+    ;; check vote complete/passed
+    (try! (is-executable))
+    ;; update vote variables
+    (var-set voteEnd block-height)
+    (var-set voteActive false)
+    ;; extend sunset height in ccd001-direct-execute
+    (try! (contract-call? .ccd001-direct-execute set-sunset-block SUNSET_BLOCK))
+    (ok true)
+  )
+)
+
+(define-public (vote-on-proposal (vote bool))
+  (let
+    (
+      (miaId (unwrap! (contract-call? .ccd004-city-registry get-city-id "mia") ERR_NO_CITY_ID))
+      (nycId (unwrap! (contract-call? .ccd004-city-registry get-city-id "nyc") ERR_NO_CITY_ID))
+      (voterId (unwrap! (contract-call? .ccd003-user-registry get-user-id contract-caller) ERR_USER_NOT_FOUND))
+      (voterRecord (map-get? UserVotes voterId))
+    )
+    ;; check that proposal is active
+    (asserts! (var-get voteActive) ERR_PROPOSAL_NOT_ACTIVE)
+    ;; check if vote record exists
+    (match voterRecord record
+      ;; if the voterRecord exists
+      (begin
+        ;; check vote is not the same as before
+        (asserts! (not (is-eq (get vote record) vote)) ERR_VOTED_ALREADY)
+        ;; record the new vote for the user
+        (map-set UserVotes voterId
+          (merge record { vote: vote })
+        )
+        ;; update the overall vote totals
+        (if vote
+          (begin
+            (var-set yesVotes (+ (var-get yesVotes) u1))
+            (var-set yesTotal (+ (var-get yesTotal) (get total record)))
+            (var-set noVotes (- (var-get noVotes) u1))
+            (var-set noTotal (- (var-get noTotal) (get total record)))
+          )
+          (begin
+            (var-set yesVotes (- (var-get yesVotes) u1))
+            (var-set yesTotal (- (var-get yesTotal) (get total record)))
+            (var-set noVotes (+ (var-get noVotes) u1))
+            (var-set noTotal (+ (var-get noTotal) (get total record)))
+          )
+        )
+      )
+      ;; if the voterRecord does not exist
+      (let
+        (
+          (scaledVoteMia (default-to u0 (get-mia-vote miaId voterId true)))
+          (scaledVoteNyc (default-to u0 (get-nyc-vote nycId voterId true)))
+          (voteMia (scale-down scaledVoteMia))
+          (voteNyc (scale-down scaledVoteNyc))
+          (voteTotal (+ voteMia voteNyc))
+        )
+        ;; record the vote for the user
+        (map-insert UserVotes voterId {
+          vote: vote,
+          mia: voteMia,
+          nyc: voteNyc,
+          total: voteTotal,
+        })
+        ;; update the overall vote totals
+        (if vote
+          (begin
+            (var-set yesVotes (+ (var-get yesVotes) u1))
+            (var-set yesTotal (+ (var-get yesTotal) voteTotal))
+          )
+          (begin
+            (var-set noVotes (+ (var-get noVotes) u1))
+            (var-set noTotal (+ (var-get noTotal) voteTotal))
+          )
+        )
+      )
+    )
+    ;; print voter information
+    (print (map-get? UserVotes voterId))
+    ;; print vote totals
+    (print (get-vote-totals))
+    (ok true)
+  )
+)
+
+;; READ ONLY FUNCTIONS
+
+(define-read-only (is-executable)
+  (begin
+    ;; check that there is at least one vote
+    (asserts! (or (> (var-get yesVotes) u0) (> (var-get noVotes) u0)) ERR_VOTE_FAILED)
+    ;; check that yes total is more than no total
+    (asserts! (> (var-get yesTotal) (var-get noTotal)) ERR_VOTE_FAILED)
+    (ok true)
+  )
+)
+
+(define-read-only (is-vote-active)
+  (some (var-get voteActive))
+)
+
+(define-read-only (get-proposal-info)
+  (some CCIP_017)
+)
+
+(define-read-only (get-vote-period)
+  (if (and
+    (> (var-get voteStart)  u0)
+    (> (var-get voteEnd) u0))
+    ;; if both are set, return values
+    (some {
+      startBlock: (var-get voteStart),
+      endBlock: (var-get voteEnd),
+      length: (- (var-get voteEnd) (var-get voteStart))
+    })
+    ;; else return none
+    none
+  )
+)
+
+(define-read-only (get-vote-totals)
+  (some {
+    yesVotes: (var-get yesVotes),
+    yesTotal: (var-get yesTotal),
+    noVotes: (var-get noVotes),
+    noTotal: (var-get noTotal)
+  })
+)
+
+(define-read-only (get-voter-info (id uint))
+  (map-get? UserVotes id)
+)
+
+;; MIA vote calculation
+;; returns (some uint) or (none)
+;; optionally scaled by VOTE_SCALE_FACTOR (10^6)
+(define-read-only (get-mia-vote (cityId uint) (userId uint) (scaled bool))
+  (let
+    (
+      ;; MAINNET: MIA cycle 64 / first block BTC 800,450 STX 114,689
+      ;; cycle 2 / u4500 used in tests
+      (cycle64Hash (unwrap! (get-block-hash u4500) none))
+      (cycle64Data (at-block cycle64Hash (contract-call? .ccd007-citycoin-stacking get-stacker cityId u2 userId)))
+      (cycle64Amount (get stacked cycle64Data))
+      ;; MAINNET: MIA cycle 65 / first block BTC 804,649 STX 118,282
+      ;; cycle 3 / u6600 used in tests
+      (cycle65Hash (unwrap! (get-block-hash u6600) none))
+      (cycle65Data (at-block cycle65Hash (contract-call? .ccd007-citycoin-stacking get-stacker cityId u3 userId)))
+      (cycle65Amount (get stacked cycle65Data))
+      ;; MIA vote calculation
+      (avgStacked (/ (+ (scale-up cycle64Amount) (scale-up cycle65Amount)) u2))
+      (scaledVote (/ (* avgStacked MIA_SCALE_FACTOR) MIA_SCALE_BASE))
+    )
+    ;; check that at least one value is positive
+    (asserts! (or (> cycle64Amount u0) (> cycle65Amount u0)) none)
+    ;; return scaled or unscaled value
+    (if scaled (some scaledVote) (some (/ scaledVote VOTE_SCALE_FACTOR)))
+  )
+)
+
+;; NYC vote calculation
+;; returns (some uint) or (none)
+;; optionally scaled by VOTE_SCALE_FACTOR (10^6)
+(define-read-only (get-nyc-vote (cityId uint) (userId uint) (scaled bool))
+  (let
+    (
+      ;; NYC cycle 64 / first block BTC 800,450 STX 114,689
+      ;; cycle 2 / u4500 used in tests
+      (cycle64Hash (unwrap! (get-block-hash u4500) none))
+      (cycle64Data (at-block cycle64Hash (contract-call? .ccd007-citycoin-stacking get-stacker cityId u2 userId)))
+      (cycle64Amount (get stacked cycle64Data))
+      ;; NYC cycle 65 / first block BTC 804,649 STX 118,282
+      ;; cycle 3 / u6600 used in tests
+      (cycle65Hash (unwrap! (get-block-hash u6600) none))
+      (cycle65Data (at-block cycle65Hash (contract-call? .ccd007-citycoin-stacking get-stacker cityId u3 userId)))
+      (cycle65Amount (get stacked cycle65Data))
+      ;; NYC vote calculation
+      (scaledVote (/ (+ (scale-up cycle64Amount) (scale-up cycle65Amount)) u2))
+    )
+    ;; check that at least one value is positive
+    (asserts! (or (> cycle64Amount u0) (> cycle65Amount u0)) none)
+    ;; return scaled or unscaled value
+    (if scaled (some scaledVote) (some (/ scaledVote VOTE_SCALE_FACTOR)))
+  )
+)
+
+;; PRIVATE FUNCTIONS
+
+;; get block hash by height
+(define-private (get-block-hash (blockHeight uint))
+  (get-block-info? id-header-hash blockHeight)
+)
+
+;; CREDIT: ALEX math-fixed-point-16.clar
+
+(define-private (scale-up (a uint))
+  (* a VOTE_SCALE_FACTOR)
+)
+
+(define-private (scale-down (a uint))
+  (/ a VOTE_SCALE_FACTOR)
+)
+
diff --git a/models/proposals/ccip017-extend-direct-execute-sunset-period.model.ts b/models/proposals/ccip017-extend-direct-execute-sunset-period.model.ts
new file mode 100644
index 00000000..84142014
--- /dev/null
+++ b/models/proposals/ccip017-extend-direct-execute-sunset-period.model.ts
@@ -0,0 +1,73 @@
+import { PROPOSALS } from "../../utils/common.ts";
+import { Chain, Account, Tx, types, ReadOnlyFn } from "../../utils/deps.ts";
+
+enum ErrCode {
+  ERR_PANIC = 1700,
+  ERR_VOTED_ALREADY,
+  ERR_NOTHING_STACKED,
+  ERR_USER_NOT_FOUND,
+  ERR_PROPOSAL_NOT_ACTIVE,
+  ERR_PROPOSAL_STILL_ACTIVE,
+  ERR_NO_CITY_ID,
+  ERR_VOTE_FAILED,
+}
+
+export class CCIP017ExtendDirectExecuteSunsetPeriod {
+  name = PROPOSALS.CCIP_017;
+  static readonly ErrCode = ErrCode;
+  chain: Chain;
+  deployer: Account;
+
+  constructor(chain: Chain, deployer: Account) {
+    this.chain = chain;
+    this.deployer = deployer;
+  }
+
+  // public functions
+
+  // execute() excluded since called by passProposal and CCD001
+
+  voteOnProposal(sender: Account, vote: boolean) {
+    return Tx.contractCall(this.name, "vote-on-proposal", [types.bool(vote)], sender.address);
+  }
+
+  // read-only functions
+
+  isExecutable() {
+    return this.callReadOnlyFn("is-executable");
+  }
+
+  isVoteActive() {
+    return this.callReadOnlyFn("is-vote-active");
+  }
+
+  getProposalInfo() {
+    return this.callReadOnlyFn("get-proposal-info");
+  }
+
+  getVotePeriod() {
+    return this.callReadOnlyFn("get-vote-period");
+  }
+
+  getVoteTotals() {
+    return this.callReadOnlyFn("get-vote-totals");
+  }
+
+  getVoterInfo(userId: number) {
+    return this.callReadOnlyFn("get-voter-info", [types.uint(userId)]);
+  }
+
+  getMiaVote(cityId: number, userId: number, scaled: boolean) {
+    return this.callReadOnlyFn("get-mia-vote", [types.uint(cityId), types.uint(userId), types.bool(scaled)]);
+  }
+
+  getNycVote(cityId: number, userId: number, scaled: boolean) {
+    return this.callReadOnlyFn("get-nyc-vote", [types.uint(cityId), types.uint(userId), types.bool(scaled)]);
+  }
+
+  // read-only function helper
+  private callReadOnlyFn(method: string, args: Array<any> = [], sender: Account = this.deployer): ReadOnlyFn {
+    const result = this.chain.callReadOnlyFn(this.name, method, args, sender?.address);
+    return result;
+  }
+}
diff --git a/tests/proposals/ccip017-extend-direct-execute-sunset-period.test.ts b/tests/proposals/ccip017-extend-direct-execute-sunset-period.test.ts
new file mode 100644
index 00000000..83424bb0
--- /dev/null
+++ b/tests/proposals/ccip017-extend-direct-execute-sunset-period.test.ts
@@ -0,0 +1,542 @@
+import { Account, Clarinet, Chain, types, assertEquals } from "../../utils/deps.ts";
+import { constructAndPassProposal, mia, nyc, passProposal, PROPOSALS } from "../../utils/common.ts";
+import { CCD006CityMining } from "../../models/extensions/ccd006-citycoin-mining.model.ts";
+import { CCD007CityStacking } from "../../models/extensions/ccd007-citycoin-stacking.model.ts";
+import { CCIP017ExtendDirectExecuteSunsetPeriod } from "../../models/proposals/ccip017-extend-direct-execute-sunset-period.model.ts";
+
+Clarinet.test({
+  name: "ccip-017: execute() fails with ERR_VOTE_FAILED if there are no votes",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+
+    // register MIA and NYC
+    constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCD004_CITY_REGISTRY_001);
+    // set activation details for MIA and NYC
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD005_CITY_DATA_001);
+    // set activation status for MIA and NYC
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD005_CITY_DATA_002);
+
+    // act
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    block.receipts[2].result.expectErr().expectUint(CCIP017ExtendDirectExecuteSunsetPeriod.ErrCode.ERR_VOTE_FAILED);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: execute() fails with ERR_VOTE_FAILED if there are more no than yes votes",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // register MIA and NYC
+    constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCD004_CITY_REGISTRY_001);
+    // set activation details for MIA and NYC
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD005_CITY_DATA_001);
+    // set activation status for MIA and NYC
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD005_CITY_DATA_002);
+    // add stacking treasury in city data
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD007_CITY_STACKING_007);
+    // mints mia to user1 and user2
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD007_CITY_STACKING_009);
+    // adds the token contract to the treasury allow list
+    passProposal(chain, accounts, PROPOSALS.TEST_CCD007_CITY_STACKING_010);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+    stackingBlock.receipts[1].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute two no votes
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, false), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    /* double check voting data
+    console.log(`voting block:\n${JSON.stringify(votingBlock, null, 2)}`);
+    console.log("user 1:");
+    console.log(ccd007CityStacking.getStacker(mia.cityId, 2, 1));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(1));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, 1, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, 1, true));
+    console.log("user 2:");
+    console.log(ccd007CityStacking.getStacker(mia.cityId, 2, 2));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(2));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, 2, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, 2, true));
+    */
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    block.receipts[2].result.expectErr().expectUint(CCIP017ExtendDirectExecuteSunsetPeriod.ErrCode.ERR_VOTE_FAILED);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: execute() succeeds if there is a single yes vote",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute single yes vote
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true)]);
+
+    /* double check voting data
+    const cycleId = 2;
+    const userId = 2;
+    console.log(`\nconstruct block:\n${JSON.stringify(constructBlock, null, 2)}`);
+    console.log(`\nmining block:\n${JSON.stringify(miningBlock, null, 2)}`);
+    console.log(`\nstacking block:\n${JSON.stringify(stackingBlock, null, 2)}`);
+    console.log(`\nvoting block:\n${JSON.stringify(votingBlock, null, 2)}`);
+    console.log("\nuser 1 mia:");
+    console.log(ccd007CityStacking.getStacker(mia.cityId, cycleId, userId));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(userId));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, userId, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, userId, true));
+    console.log("\nuser 1 nyc:");
+    console.log(ccd007CityStacking.getStacker(nyc.cityId, cycleId, userId));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(userId));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, userId, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, userId, true));
+    */
+
+    // check vote is active
+    ccip017ExtendDirectExecuteSunsetPeriod.isVoteActive().result.expectSome().expectBool(true);
+    // check proposal info
+    const proposalInfo = {
+      hash: types.ascii("7ddbf6152790a730faa059b564a8524abc3c70d3"),
+      link: types.ascii("https://github.com/citycoins/governance/blob/feat/add-ccip-017/ccips/ccip-017/ccip-017-extend-direct-execute-sunset-period.md"),
+      name: types.ascii("Extend Direct Execute Sunset Period"),
+    };
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getProposalInfo().result.expectSome().expectTuple(), proposalInfo);
+    // check vote period is not set (end unknown)
+    ccip017ExtendDirectExecuteSunsetPeriod.getVotePeriod().result.expectNone();
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    // check vote period is set and returns
+    const start = constructBlock.height - CCD007CityStacking.FIRST_STACKING_BLOCK - 1;
+    const end = votingBlock.height;
+    const votingPeriod = {
+      startBlock: types.uint(start),
+      endBlock: types.uint(end),
+      length: types.uint(end - start),
+    };
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVotePeriod().result.expectSome().expectTuple(), votingPeriod);
+    // check vote is no longer active
+    ccip017ExtendDirectExecuteSunsetPeriod.isVoteActive().result.expectSome().expectBool(false);
+    //console.log(`\nexecute block:\n${JSON.stringify(block, null, 2)}`);
+    // check that proposal executed
+    block.receipts[2].result.expectOk().expectUint(3);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: execute() succeeds if there are more yes than no votes",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute yes and no vote
+    // user 1 has more voting power
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    /* double check voting data
+    const cycleId = 2;
+    const user1Id = 2;
+    const user2Id = 3;
+    console.log(`\nconstruct block:\n${JSON.stringify(constructBlock, null, 2)}`);
+    console.log(`\nmining block:\n${JSON.stringify(miningBlock, null, 2)}`);
+    console.log(`\nstacking block:\n${JSON.stringify(stackingBlock, null, 2)}`);
+    console.log(`\nvoting block:\n${JSON.stringify(votingBlock, null, 2)}`);
+    console.log("\nuser 1 mia:");
+    console.log(ccd007CityStacking.getStacker(mia.cityId, cycleId, user1Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user1Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, user1Id, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, user1Id, true));
+    console.log("\nuser 1 nyc:");
+    console.log(ccd007CityStacking.getStacker(nyc.cityId, cycleId, user1Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user1Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, user1Id, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, user1Id, true));
+    console.log("\nuser 2 mia:");
+    console.log(ccd007CityStacking.getStacker(mia.cityId, cycleId, user2Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user2Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, user2Id, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getMiaVote(mia.cityId, user2Id, true));
+    console.log("\nuser 2 nyc:");
+    console.log(ccd007CityStacking.getStacker(nyc.cityId, cycleId, user2Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user2Id));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, user2Id, false));
+    console.log(ccip017ExtendDirectExecuteSunsetPeriod.getNycVote(nyc.cityId, user2Id, true));
+    */
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    //console.log(`\nexecute block:\n${JSON.stringify(block, null, 2)}`);
+    block.receipts[2].result.expectOk().expectUint(3);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: execute() succeeds if there are more yes than no votes after a reversal",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute yes and no vote
+    // user 1 has more voting power
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, false), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, true)]);
+
+    // switch yes and no vote
+    const votingBlockReverse = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    /* double check voting data
+    console.log(`\nvoting block:\n${JSON.stringify(votingBlock, null, 2)}`);
+    console.log(`\nvoting block reverse:\n${JSON.stringify(votingBlockReverse, null, 2)}`);
+    */
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    //console.log(`\nexecute block:\n${JSON.stringify(block, null, 2)}`);
+    block.receipts[2].result.expectOk().expectUint(3);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: vote-on-proposal() fails with ERR_USER_NOT_FOUND if user is not registered in ccd003-user-registry",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const user3 = accounts.get("wallet_3")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute yes and no vote
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user3, true)]);
+
+    // assert
+    //console.log(`votingBlock: ${JSON.stringify(votingBlock, null, 2)}`);
+    votingBlock.receipts[0].result.expectErr().expectUint(CCIP017ExtendDirectExecuteSunsetPeriod.ErrCode.ERR_USER_NOT_FOUND);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: vote-on-proposal() fails with ERR_PROPOSAL_NOT_ACTIVE if called after the vote ends",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // execute yes and no vote
+    // user 1 has more voting power
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    // execute ccip-017
+    passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // act
+    const votingBlock2 = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true)]);
+
+    // assert
+    votingBlock2.receipts[0].result.expectErr().expectUint(CCIP017ExtendDirectExecuteSunsetPeriod.ErrCode.ERR_PROPOSAL_NOT_ACTIVE);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: vote-on-proposal() fails with ERR_VOTED_ALREADY if user already voted with the same value",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // execute yes and no vote
+    // user 1 has more voting power
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    // act
+    const votingBlock2 = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true)]);
+
+    // assert
+    votingBlock2.receipts[0].result.expectErr().expectUint(CCIP017ExtendDirectExecuteSunsetPeriod.ErrCode.ERR_VOTED_ALREADY);
+  },
+});
+
+Clarinet.test({
+  name: "ccip-017: read-only functions return expected values before/after reversal",
+  fn(chain: Chain, accounts: Map<string, Account>) {
+    // arrange
+    const sender = accounts.get("deployer")!;
+    const user1 = accounts.get("wallet_1")!;
+    const user2 = accounts.get("wallet_2")!;
+    const ccd006CityMining = new CCD006CityMining(chain, sender, "ccd006-citycoin-mining");
+    const ccd007CityStacking = new CCD007CityStacking(chain, sender, "ccd007-citycoin-stacking");
+    const ccip017ExtendDirectExecuteSunsetPeriod = new CCIP017ExtendDirectExecuteSunsetPeriod(chain, sender);
+
+    const miningEntries = [25000000, 25000000];
+    const amountStacked = 500;
+    const lockPeriod = 10;
+
+    const cycleId = 2;
+    const user1Id = 2;
+    const user2Id = 3;
+
+    // progress the chain to avoid underflow in
+    // stacking reward cycle calculation
+    chain.mineEmptyBlockUntil(CCD007CityStacking.FIRST_STACKING_BLOCK);
+    // prepare for ccip-017
+    const constructBlock = constructAndPassProposal(chain, accounts, PROPOSALS.TEST_CCIP014_POX3_001);
+
+    // mine to put funds in the mining treasury
+    const miningBlock = chain.mineBlock([ccd006CityMining.mine(sender, mia.cityName, miningEntries), ccd006CityMining.mine(sender, nyc.cityName, miningEntries)]);
+
+    // stack first cycle u1, last cycle u10
+    const stackingBlock = chain.mineBlock([ccd007CityStacking.stack(user1, mia.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user1, nyc.cityName, amountStacked, lockPeriod), ccd007CityStacking.stack(user2, mia.cityName, amountStacked / 2, lockPeriod), ccd007CityStacking.stack(user2, nyc.cityName, amountStacked / 2, lockPeriod)]);
+    stackingBlock.receipts[0].result.expectOk().expectBool(true);
+
+    // progress the chain to cycle 5
+    // votes are counted in cycles 2-3
+    // past payouts tested for cycles 1-4
+    chain.mineEmptyBlockUntil(CCD007CityStacking.REWARD_CYCLE_LENGTH * 6 + 10);
+    ccd007CityStacking.getCurrentRewardCycle().result.expectUint(5);
+
+    // act
+
+    // execute yes and no vote
+    // user 1 has more voting power
+    const votingBlock = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, false), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, true)]);
+
+    // assert
+
+    // overall totals
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoteTotals().result.expectSome().expectTuple(), { noTotal: types.uint(938), noVotes: types.uint(1), yesTotal: types.uint(469), yesVotes: types.uint(1) });
+    // user 1
+    assertEquals(ccd007CityStacking.getStacker(mia.cityId, cycleId, user1Id).result.expectTuple(), { claimable: types.uint(0), stacked: types.uint(500) });
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user1Id).result.expectSome().expectTuple(), { mia: types.uint(438), nyc: types.uint(500), total: types.uint(938), vote: types.bool(false) });
+    // user 2
+    assertEquals(ccd007CityStacking.getStacker(mia.cityId, cycleId, user2Id).result.expectTuple(), { claimable: types.uint(0), stacked: types.uint(250) });
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user2Id).result.expectSome().expectTuple(), { mia: types.uint(219), nyc: types.uint(250), total: types.uint(469), vote: types.bool(true) });
+
+    // act
+
+    // switch yes and no vote
+    const votingBlockReverse = chain.mineBlock([ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user1, true), ccip017ExtendDirectExecuteSunsetPeriod.voteOnProposal(user2, false)]);
+
+    // assert
+
+    // overall totals
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoteTotals().result.expectSome().expectTuple(), { noTotal: types.uint(469), noVotes: types.uint(1), yesTotal: types.uint(938), yesVotes: types.uint(1) });
+    // user 1
+    assertEquals(ccd007CityStacking.getStacker(mia.cityId, cycleId, user1Id).result.expectTuple(), { claimable: types.uint(0), stacked: types.uint(500) });
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user1Id).result.expectSome().expectTuple(), { mia: types.uint(438), nyc: types.uint(500), total: types.uint(938), vote: types.bool(true) });
+    // user 2
+    assertEquals(ccd007CityStacking.getStacker(mia.cityId, cycleId, user2Id).result.expectTuple(), { claimable: types.uint(0), stacked: types.uint(250) });
+    assertEquals(ccip017ExtendDirectExecuteSunsetPeriod.getVoterInfo(user2Id).result.expectSome().expectTuple(), { mia: types.uint(219), nyc: types.uint(250), total: types.uint(469), vote: types.bool(false) });
+
+    // execute ccip-017
+    const block = passProposal(chain, accounts, PROPOSALS.CCIP_017);
+
+    // assert
+    //console.log(`\nexecute block:\n${JSON.stringify(block, null, 2)}`);
+    block.receipts[2].result.expectOk().expectUint(3);
+  },
+});
diff --git a/utils/common.ts b/utils/common.ts
index cf496c12..60b1869d 100644
--- a/utils/common.ts
+++ b/utils/common.ts
@@ -39,6 +39,7 @@ export const PROPOSALS = {
   CCIP_013: ADDRESS.concat(".ccip013-migration"),
   CCIP_014: ADDRESS.concat(".ccip014-pox-3"),
   CCIP_014_V2: ADDRESS.concat(".ccip014-pox-3-v2"),
+  CCIP_017: ADDRESS.concat(".ccip017"),
   TEST_CCD001_DIRECT_EXECUTE_001: ADDRESS.concat(".test-ccd001-direct-execute-001"),
   TEST_CCD001_DIRECT_EXECUTE_002: ADDRESS.concat(".test-ccd001-direct-execute-002"),
   TEST_CCD001_DIRECT_EXECUTE_003: ADDRESS.concat(".test-ccd001-direct-execute-003"),