@boostxyz/sdk 2.1.2 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/Actions/Action.cjs +1 -1
  2. package/dist/Actions/Action.cjs.map +1 -1
  3. package/dist/Actions/Action.d.ts +2 -1
  4. package/dist/Actions/Action.d.ts.map +1 -1
  5. package/dist/Actions/Action.js +14 -13
  6. package/dist/Actions/Action.js.map +1 -1
  7. package/dist/Actions/EventAction.cjs +1 -1
  8. package/dist/Actions/EventAction.cjs.map +1 -1
  9. package/dist/Actions/EventAction.d.ts +219 -0
  10. package/dist/Actions/EventAction.d.ts.map +1 -1
  11. package/dist/Actions/EventAction.js +39 -39
  12. package/dist/Actions/EventAction.js.map +1 -1
  13. package/dist/AllowLists/AllowList.cjs +1 -1
  14. package/dist/AllowLists/AllowList.cjs.map +1 -1
  15. package/dist/AllowLists/AllowList.d.ts +2 -1
  16. package/dist/AllowLists/AllowList.d.ts.map +1 -1
  17. package/dist/AllowLists/AllowList.js +18 -17
  18. package/dist/AllowLists/AllowList.js.map +1 -1
  19. package/dist/AllowLists/SimpleAllowList.cjs +1 -1
  20. package/dist/AllowLists/SimpleAllowList.js +9 -9
  21. package/dist/AllowLists/SimpleDenyList.cjs +1 -1
  22. package/dist/AllowLists/SimpleDenyList.js +2 -2
  23. package/dist/Auth/PassthroughAuth.cjs +1 -1
  24. package/dist/Auth/PassthroughAuth.js +1 -1
  25. package/dist/BoostCore.cjs +2 -2
  26. package/dist/BoostCore.cjs.map +1 -1
  27. package/dist/BoostCore.d.ts +33 -0
  28. package/dist/BoostCore.d.ts.map +1 -1
  29. package/dist/BoostCore.js +98 -81
  30. package/dist/BoostCore.js.map +1 -1
  31. package/dist/BoostRegistry.cjs +1 -1
  32. package/dist/BoostRegistry.js +12 -12
  33. package/dist/{Budget-CUIFf1Br.js → Budget-2Zb3Mb-8.js} +40 -39
  34. package/dist/{Budget-CUIFf1Br.js.map → Budget-2Zb3Mb-8.js.map} +1 -1
  35. package/dist/{Budget-cI30CIMN.cjs → Budget-2xKEvkbL.cjs} +2 -2
  36. package/dist/{Budget-cI30CIMN.cjs.map → Budget-2xKEvkbL.cjs.map} +1 -1
  37. package/dist/Budgets/Budget.cjs +1 -1
  38. package/dist/Budgets/Budget.d.ts +2 -1
  39. package/dist/Budgets/Budget.d.ts.map +1 -1
  40. package/dist/Budgets/Budget.js +3 -3
  41. package/dist/Budgets/ManagedBudget.cjs +1 -1
  42. package/dist/Budgets/ManagedBudget.js +33 -33
  43. package/dist/Deployable/DeployableTarget.cjs +1 -1
  44. package/dist/Deployable/DeployableTarget.js +1 -1
  45. package/dist/Deployable/DeployableTargetWithRBAC.cjs +1 -1
  46. package/dist/Deployable/DeployableTargetWithRBAC.cjs.map +1 -1
  47. package/dist/Deployable/DeployableTargetWithRBAC.d.ts +158 -0
  48. package/dist/Deployable/DeployableTargetWithRBAC.d.ts.map +1 -1
  49. package/dist/Deployable/DeployableTargetWithRBAC.js +268 -31
  50. package/dist/Deployable/DeployableTargetWithRBAC.js.map +1 -1
  51. package/dist/Incentive-CfIjdL4T.cjs +2 -0
  52. package/dist/Incentive-CfIjdL4T.cjs.map +1 -0
  53. package/dist/Incentive-DNwTyG5Z.js +393 -0
  54. package/dist/Incentive-DNwTyG5Z.js.map +1 -0
  55. package/dist/Incentives/AllowListIncentive.cjs +1 -1
  56. package/dist/Incentives/AllowListIncentive.cjs.map +1 -1
  57. package/dist/Incentives/AllowListIncentive.js +28 -28
  58. package/dist/Incentives/AllowListIncentive.js.map +1 -1
  59. package/dist/Incentives/CGDAIncentive.cjs +1 -1
  60. package/dist/Incentives/CGDAIncentive.cjs.map +1 -1
  61. package/dist/Incentives/CGDAIncentive.d.ts +30 -0
  62. package/dist/Incentives/CGDAIncentive.d.ts.map +1 -1
  63. package/dist/Incentives/CGDAIncentive.js +25 -25
  64. package/dist/Incentives/CGDAIncentive.js.map +1 -1
  65. package/dist/Incentives/ERC1155Incentive.d.ts +25 -0
  66. package/dist/Incentives/ERC1155Incentive.d.ts.map +1 -1
  67. package/dist/Incentives/ERC20Incentive.cjs +1 -1
  68. package/dist/Incentives/ERC20Incentive.cjs.map +1 -1
  69. package/dist/Incentives/ERC20Incentive.d.ts +30 -0
  70. package/dist/Incentives/ERC20Incentive.d.ts.map +1 -1
  71. package/dist/Incentives/ERC20Incentive.js +42 -42
  72. package/dist/Incentives/ERC20Incentive.js.map +1 -1
  73. package/dist/Incentives/ERC20PeggedIncentive.d.ts +905 -0
  74. package/dist/Incentives/ERC20PeggedIncentive.d.ts.map +1 -0
  75. package/dist/Incentives/ERC20VariableCriteriaIncentive.cjs +1 -1
  76. package/dist/Incentives/ERC20VariableCriteriaIncentive.cjs.map +1 -1
  77. package/dist/Incentives/ERC20VariableCriteriaIncentive.d.ts +67 -0
  78. package/dist/Incentives/ERC20VariableCriteriaIncentive.d.ts.map +1 -1
  79. package/dist/Incentives/ERC20VariableCriteriaIncentive.js +31 -31
  80. package/dist/Incentives/ERC20VariableCriteriaIncentive.js.map +1 -1
  81. package/dist/Incentives/ERC20VariableIncentive.cjs +1 -1
  82. package/dist/Incentives/ERC20VariableIncentive.cjs.map +1 -1
  83. package/dist/Incentives/ERC20VariableIncentive.d.ts +20 -0
  84. package/dist/Incentives/ERC20VariableIncentive.d.ts.map +1 -1
  85. package/dist/Incentives/ERC20VariableIncentive.js +18 -18
  86. package/dist/Incentives/ERC20VariableIncentive.js.map +1 -1
  87. package/dist/Incentives/Incentive.cjs +1 -1
  88. package/dist/Incentives/Incentive.cjs.map +1 -1
  89. package/dist/Incentives/Incentive.d.ts +26 -4
  90. package/dist/Incentives/Incentive.d.ts.map +1 -1
  91. package/dist/Incentives/Incentive.js +20 -40
  92. package/dist/Incentives/Incentive.js.map +1 -1
  93. package/dist/Incentives/PointsIncentive.cjs +1 -1
  94. package/dist/Incentives/PointsIncentive.cjs.map +1 -1
  95. package/dist/Incentives/PointsIncentive.d.ts +25 -0
  96. package/dist/Incentives/PointsIncentive.d.ts.map +1 -1
  97. package/dist/Incentives/PointsIncentive.js +29 -29
  98. package/dist/Incentives/PointsIncentive.js.map +1 -1
  99. package/dist/{SimpleDenyList-Ds0CQ4uR.cjs → SimpleDenyList-BQ0GqQfC.cjs} +2 -2
  100. package/dist/{SimpleDenyList-Ds0CQ4uR.cjs.map → SimpleDenyList-BQ0GqQfC.cjs.map} +1 -1
  101. package/dist/{SimpleDenyList-CXruWOyu.js → SimpleDenyList-D49GF3NX.js} +11 -11
  102. package/dist/{SimpleDenyList-CXruWOyu.js.map → SimpleDenyList-D49GF3NX.js.map} +1 -1
  103. package/dist/Validators/LimitedSignerValidator.cjs +1 -1
  104. package/dist/Validators/LimitedSignerValidator.d.ts +10 -0
  105. package/dist/Validators/LimitedSignerValidator.d.ts.map +1 -1
  106. package/dist/Validators/LimitedSignerValidator.js +15 -15
  107. package/dist/Validators/SignerValidator.cjs +1 -1
  108. package/dist/Validators/SignerValidator.cjs.map +1 -1
  109. package/dist/Validators/SignerValidator.d.ts +10 -0
  110. package/dist/Validators/SignerValidator.d.ts.map +1 -1
  111. package/dist/Validators/SignerValidator.js +8 -8
  112. package/dist/Validators/SignerValidator.js.map +1 -1
  113. package/dist/Validators/Validator.cjs +1 -1
  114. package/dist/Validators/Validator.cjs.map +1 -1
  115. package/dist/Validators/Validator.d.ts +2 -1
  116. package/dist/Validators/Validator.d.ts.map +1 -1
  117. package/dist/Validators/Validator.js +20 -19
  118. package/dist/Validators/Validator.js.map +1 -1
  119. package/dist/{componentInterfaces-dakxtQHf.js → componentInterfaces-C4uAYjSo.js} +12 -12
  120. package/dist/componentInterfaces-C4uAYjSo.js.map +1 -0
  121. package/dist/deployments.json +3 -0
  122. package/dist/{generated-BTslM7bf.js → generated-B5pVBNb-.js} +1835 -730
  123. package/dist/generated-B5pVBNb-.js.map +1 -0
  124. package/dist/generated-D50hBWtr.cjs +3 -0
  125. package/dist/generated-D50hBWtr.cjs.map +1 -0
  126. package/dist/index.cjs +1 -1
  127. package/dist/index.d.ts +1 -0
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +113 -110
  130. package/package.json +2 -2
  131. package/src/Actions/Action.ts +3 -0
  132. package/src/Actions/EventAction.test.ts +49 -2
  133. package/src/Actions/EventAction.ts +12 -4
  134. package/src/AllowLists/AllowList.ts +3 -0
  135. package/src/BoostCore.ts +27 -5
  136. package/src/Budgets/Budget.ts +3 -0
  137. package/src/Deployable/DeployableTargetWithRBAC.test.ts +175 -0
  138. package/src/Deployable/DeployableTargetWithRBAC.ts +281 -0
  139. package/src/Incentives/AllowListIncentive.test.ts +48 -0
  140. package/src/Incentives/AllowListIncentive.ts +1 -1
  141. package/src/Incentives/CGDAIncentive.test.ts +50 -0
  142. package/src/Incentives/CGDAIncentive.ts +1 -1
  143. package/src/Incentives/ERC1155Incentive.ts +1 -1
  144. package/src/Incentives/ERC20Incentive.test.ts +49 -0
  145. package/src/Incentives/ERC20Incentive.ts +1 -1
  146. package/src/Incentives/ERC20PeggedIncentive.ts +507 -0
  147. package/src/Incentives/ERC20VariableIncentive.test.ts +48 -0
  148. package/src/Incentives/ERC20VariableIncentive.ts +1 -1
  149. package/src/Incentives/Incentive.ts +9 -1
  150. package/src/Incentives/PointsIncentive.test.ts +51 -0
  151. package/src/Incentives/PointsIncentive.ts +1 -1
  152. package/src/Validators/Validator.ts +3 -0
  153. package/src/index.ts +1 -0
  154. package/dist/componentInterfaces-dakxtQHf.js.map +0 -1
  155. package/dist/generated-BTslM7bf.js.map +0 -1
  156. package/dist/generated-DhXxTQ20.cjs +0 -3
  157. package/dist/generated-DhXxTQ20.cjs.map +0 -1
@@ -8,6 +8,11 @@ import {
8
8
  freshManagedBudget,
9
9
  } from '@boostxyz/test/helpers';
10
10
  import { Roles } from './DeployableTargetWithRBAC';
11
+ import { createTestClient, http, zeroAddress, publicActions, walletActions } from 'viem';
12
+ import { setupConfig } from '@boostxyz/test/viem';
13
+ import { privateKeyToAccount } from 'viem/accounts';
14
+ import { hardhat } from 'viem/chains';
15
+ import { ManagedBudget } from '../Budgets/ManagedBudget';
11
16
 
12
17
  let fixtures: Fixtures;
13
18
 
@@ -100,4 +105,174 @@ describe('RBAC', () => {
100
105
  expect(await budget.isAuthorized(user)).toBe(false);
101
106
  expect(await budget.rolesOf(user)).not.toContain(Roles.MANAGER);
102
107
  });
108
+
109
+ test('can transfer ownership', async () => {
110
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
111
+ const oldOwner = accounts[0].account;
112
+ const newOwner = accounts[9].account;
113
+ expect(await budget.owner()).toBe(oldOwner);
114
+
115
+ // Transfer ownership
116
+ await budget.transferOwnership(newOwner);
117
+
118
+ // Verify the new owner
119
+ expect(await budget.owner()).toBe(newOwner);
120
+ });
121
+
122
+ test('can renounce ownership', async () => {
123
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
124
+ const owner = accounts[0].account;
125
+
126
+ // Verify initial owner
127
+ expect(await budget.owner()).toBe(owner);
128
+
129
+ // Renounce ownership
130
+ await budget.renounceOwnership();
131
+ const newOwner = await budget.owner();
132
+ expect(newOwner).toBe(zeroAddress);
133
+ });
134
+
135
+ test('supports two-step ownership handover', async () => {
136
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
137
+ const currentOwner = accounts[0].account;
138
+ const newOwner = accounts[6];
139
+
140
+ const walletClient = createTestClient({
141
+ transport: http('http://127.0.0.1:8545', { retryCount: 0 }),
142
+ chain: hardhat,
143
+ mode: 'hardhat',
144
+ account: privateKeyToAccount(newOwner.key),
145
+ key: newOwner.key,
146
+ })
147
+ .extend(publicActions)
148
+ .extend(walletActions) as any;
149
+
150
+ const newOwnerOptions = {
151
+ account: privateKeyToAccount(newOwner.key),
152
+ config: setupConfig(walletClient)
153
+ };
154
+
155
+ // Create budget instance with different signer
156
+ const sameBudgetDifferentSigner = new ManagedBudget(
157
+ { config: newOwnerOptions.config, account: newOwnerOptions.account },
158
+ budget.address
159
+ );
160
+
161
+ await sameBudgetDifferentSigner.requestOwnershipHandover();
162
+
163
+ // Verify initial ownership
164
+ expect(await budget.owner()).toBe(currentOwner);
165
+ expect(await sameBudgetDifferentSigner.owner()).toBe(currentOwner);
166
+
167
+ // Complete handover from current owner
168
+ await budget.completeOwnershipHandover(newOwner.account);
169
+
170
+ // Verify ownership has transferred
171
+ expect(await budget.owner()).toBe(newOwner.account);
172
+ expect(await sameBudgetDifferentSigner.owner()).toBe(newOwner.account);
173
+ });
174
+
175
+ test('ownership handover fails if not requested', async () => {
176
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
177
+ const newOwner = accounts[7].account;
178
+
179
+ // Try to complete handover without request
180
+ await expect(
181
+ budget.completeOwnershipHandover(newOwner)
182
+ ).rejects.toThrowError('NoHandoverRequest');
183
+ });
184
+
185
+ test('can cancel ownership handover', async () => {
186
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
187
+ const currentOwner = accounts[0].account;
188
+ const newOwner = accounts[6];
189
+
190
+ // Create new options with new owner's account and wallet client
191
+ const walletClient = createTestClient({
192
+ transport: http('http://127.0.0.1:8545', { retryCount: 0 }),
193
+ chain: hardhat,
194
+ mode: 'hardhat',
195
+ account: privateKeyToAccount(newOwner.key),
196
+ key: newOwner.key,
197
+ })
198
+ .extend(publicActions)
199
+ .extend(walletActions) as any;
200
+
201
+ const newOwnerOptions = {
202
+ account: privateKeyToAccount(newOwner.key),
203
+ config: setupConfig(walletClient)
204
+ };
205
+
206
+ // Create budget instance with different signer
207
+ const sameBudgetDifferentSigner = new ManagedBudget(
208
+ { config: newOwnerOptions.config, account: newOwnerOptions.account },
209
+ budget.address
210
+ );
211
+
212
+ // Request handover from new owner account
213
+ await sameBudgetDifferentSigner.requestOwnershipHandover();
214
+
215
+ // Cancel the handover request
216
+ await sameBudgetDifferentSigner.cancelOwnershipHandover();
217
+
218
+ // Verify handover can't be completed after cancellation
219
+ await expect(
220
+ budget.completeOwnershipHandover(newOwner.account)
221
+ ).rejects.toThrowError('NoHandoverRequest');
222
+
223
+ // Verify ownership hasn't changed
224
+ expect(await budget.owner()).toBe(currentOwner);
225
+ });
226
+
227
+ test('can check ownership handover expiry', async () => {
228
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
229
+ const newOwner = accounts[6];
230
+
231
+ const walletClient = createTestClient({
232
+ transport: http('http://127.0.0.1:8545', { retryCount: 0 }),
233
+ chain: hardhat,
234
+ mode: 'hardhat',
235
+ account: privateKeyToAccount(newOwner.key),
236
+ key: newOwner.key,
237
+ })
238
+ .extend(publicActions)
239
+ .extend(walletActions) as any;
240
+
241
+ const newOwnerOptions = {
242
+ account: privateKeyToAccount(newOwner.key),
243
+ config: setupConfig(walletClient)
244
+ };
245
+
246
+ // Create identical budget instance with different signer
247
+ const sameBudgetDifferentSigner = new ManagedBudget(
248
+ { config: newOwnerOptions.config, account: newOwnerOptions.account },
249
+ budget.address
250
+ );
251
+
252
+ // Check expiry before request (should be 0)
253
+ expect(await budget.ownershipHandoverExpiresAt(newOwner.account)).toBe(0n);
254
+
255
+ // Request handover
256
+ await sameBudgetDifferentSigner.requestOwnershipHandover();
257
+
258
+ // Check expiry after request (should be > 0)
259
+ const expiryTime = await budget.ownershipHandoverExpiresAt(newOwner.account);
260
+ expect(expiryTime).toBeGreaterThan(0n);
261
+
262
+ // Cancel request
263
+ await sameBudgetDifferentSigner.cancelOwnershipHandover();
264
+
265
+ // Check expiry after cancellation (should be 0 again)
266
+ expect(await budget.ownershipHandoverExpiresAt(newOwner.account)).toBe(0n);
267
+ });
268
+
269
+ test('can renounce roles', async () => {
270
+ const budget = await loadFixture(freshManagedBudget(defaultOptions, fixtures));
271
+ const account = accounts[0];
272
+ const user = account.account;
273
+
274
+ expect(await budget.hasAllRoles(user, Roles.ADMIN)).toBe(true);
275
+ await budget.renounceRoles(Roles.ADMIN);
276
+ expect(await budget.hasAllRoles(user, Roles.ADMIN)).toBe(false);
277
+ });
103
278
  });
@@ -1,14 +1,27 @@
1
1
  import {
2
2
  rbacAbi,
3
+ readOwnableRolesOwnershipHandoverExpiresAt,
3
4
  readRbacHasAllRoles,
4
5
  readRbacHasAnyRole,
5
6
  readRbacIsAuthorized,
6
7
  readRbacRolesOf,
8
+ simulateOwnableRolesCancelOwnershipHandover,
9
+ simulateOwnableRolesCompleteOwnershipHandover,
10
+ simulateOwnableRolesRenounceOwnership,
11
+ simulateOwnableRolesRenounceRoles,
12
+ simulateOwnableRolesRequestOwnershipHandover,
13
+ simulateOwnableRolesTransferOwnership,
7
14
  simulateRbacGrantManyRoles,
8
15
  simulateRbacGrantRoles,
9
16
  simulateRbacRevokeManyRoles,
10
17
  simulateRbacRevokeRoles,
11
18
  simulateRbacSetAuthorized,
19
+ writeOwnableRolesCancelOwnershipHandover,
20
+ writeOwnableRolesCompleteOwnershipHandover,
21
+ writeOwnableRolesRenounceOwnership,
22
+ writeOwnableRolesRenounceRoles,
23
+ writeOwnableRolesRequestOwnershipHandover,
24
+ writeOwnableRolesTransferOwnership,
12
25
  writeRbacGrantManyRoles,
13
26
  writeRbacGrantRoles,
14
27
  writeRbacRevokeManyRoles,
@@ -426,4 +439,272 @@ export class DeployableTargetWithRBAC<
426
439
  ...(params as any),
427
440
  });
428
441
  }
442
+
443
+ /**
444
+ * Transfer ownership of the contract to a new address
445
+ *
446
+ * @public
447
+ * @async
448
+ * @param {Address} newOwner - The address to transfer ownership to
449
+ * @param {?WriteParams} [params]
450
+ * @returns {Promise<void>}
451
+ */
452
+ public async transferOwnership(newOwner: Address, params?: WriteParams) {
453
+ return await this.awaitResult(this.transferOwnershipRaw(newOwner, params));
454
+ }
455
+
456
+ /**
457
+ * Transfer ownership of the contract to a new address
458
+ *
459
+ * @public
460
+ * @async
461
+ * @param {Address} newOwner - The address to transfer ownership to
462
+ * @param {?WriteParams} [params]
463
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
464
+ */
465
+ public async transferOwnershipRaw(newOwner: Address, params?: WriteParams) {
466
+ const { request, result } = await simulateOwnableRolesTransferOwnership(
467
+ this._config,
468
+ {
469
+ address: this.assertValidAddress(),
470
+ args: [newOwner],
471
+ ...this.optionallyAttachAccount(),
472
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
473
+ ...(params as any),
474
+ },
475
+ );
476
+ const hash = await writeOwnableRolesTransferOwnership(
477
+ this._config,
478
+ request,
479
+ );
480
+ return { hash, result };
481
+ }
482
+
483
+ /**
484
+ * Renounce ownership of the contract
485
+ *
486
+ * @public
487
+ * @async
488
+ * @param {?WriteParams} [params]
489
+ * @returns {Promise<void>}
490
+ */
491
+ public async renounceOwnership(params?: WriteParams) {
492
+ return await this.awaitResult(this.renounceOwnershipRaw(params));
493
+ }
494
+
495
+ /**
496
+ * Renounce ownership of the contract
497
+ *
498
+ * @public
499
+ * @async
500
+ * @param {?WriteParams} [params]
501
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
502
+ */
503
+ public async renounceOwnershipRaw(params?: WriteParams) {
504
+ const { request, result } = await simulateOwnableRolesRenounceOwnership(
505
+ this._config,
506
+ {
507
+ address: this.assertValidAddress(),
508
+ ...this.optionallyAttachAccount(),
509
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
510
+ ...(params as any),
511
+ },
512
+ );
513
+ const hash = await writeOwnableRolesRenounceOwnership(
514
+ this._config,
515
+ request,
516
+ );
517
+ return { hash, result };
518
+ }
519
+
520
+ /**
521
+ * Request a two-step ownership handover to the caller
522
+ * The request will automatically expire in 48 hours
523
+ *
524
+ * Note: This is part of a two-step ownership transfer process:
525
+ * 1. New owner calls requestOwnershipHandover()
526
+ * 2. Current owner calls completeOwnershipHandover(newOwner)
527
+ *
528
+ * @public
529
+ * @async
530
+ * @param {?WriteParams} [params]
531
+ * @returns {Promise<void>}
532
+ */
533
+ public async requestOwnershipHandover(params?: WriteParams) {
534
+ return await this.awaitResult(this.requestOwnershipHandoverRaw(params));
535
+ }
536
+
537
+ /**
538
+ * Request a two-step ownership handover to the caller
539
+ * The request will automatically expire in 48 hours
540
+ *
541
+ * @public
542
+ * @async
543
+ * @param {?WriteParams} [params]
544
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
545
+ */
546
+ public async requestOwnershipHandoverRaw(params?: WriteParams) {
547
+ const { request, result } =
548
+ await simulateOwnableRolesRequestOwnershipHandover(this._config, {
549
+ address: this.assertValidAddress(),
550
+ ...this.optionallyAttachAccount(),
551
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
552
+ ...(params as any),
553
+ });
554
+ const hash = await writeOwnableRolesRequestOwnershipHandover(
555
+ this._config,
556
+ request,
557
+ );
558
+ return { hash, result };
559
+ }
560
+
561
+ /**
562
+ * Complete a pending ownership handover to a new owner
563
+ * Must be called by the current owner after the new owner has requested the handover
564
+ *
565
+ * Note: This is part of a two-step ownership transfer process:
566
+ * 1. New owner calls requestOwnershipHandover()
567
+ * 2. Current owner calls completeOwnershipHandover(newOwner)
568
+ *
569
+ * @public
570
+ * @async
571
+ * @param {Address} pendingOwner - The address that requested the ownership handover
572
+ * @param {?WriteParams} [params]
573
+ * @returns {Promise<void>}
574
+ */
575
+ public async completeOwnershipHandover(
576
+ pendingOwner: Address,
577
+ params?: WriteParams,
578
+ ) {
579
+ return await this.awaitResult(
580
+ this.completeOwnershipHandoverRaw(pendingOwner, params),
581
+ );
582
+ }
583
+
584
+ /**
585
+ * Complete a pending ownership handover to a new owner
586
+ * Must be called by the current owner after the new owner has requested the handover
587
+ *
588
+ * @public
589
+ * @async
590
+ * @param {Address} pendingOwner - The address that requested the ownership handover
591
+ * @param {?WriteParams} [params]
592
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
593
+ */
594
+ public async completeOwnershipHandoverRaw(
595
+ pendingOwner: Address,
596
+ params?: WriteParams,
597
+ ) {
598
+ const { request, result } =
599
+ await simulateOwnableRolesCompleteOwnershipHandover(this._config, {
600
+ address: this.assertValidAddress(),
601
+ args: [pendingOwner],
602
+ ...this.optionallyAttachAccount(),
603
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
604
+ ...(params as any),
605
+ });
606
+ const hash = await writeOwnableRolesCompleteOwnershipHandover(
607
+ this._config,
608
+ request,
609
+ );
610
+ return { hash, result };
611
+ }
612
+
613
+ /**
614
+ * Cancel a pending ownership handover request
615
+ * Must be called by the account that originally requested the handover
616
+ *
617
+ * @public
618
+ * @async
619
+ * @param {?WriteParams} [params]
620
+ * @returns {Promise<void>}
621
+ */
622
+ public async cancelOwnershipHandover(params?: WriteParams) {
623
+ return await this.awaitResult(this.cancelOwnershipHandoverRaw(params));
624
+ }
625
+
626
+ /**
627
+ * Cancel a pending ownership handover request
628
+ * Must be called by the account that originally requested the handover
629
+ *
630
+ * @public
631
+ * @async
632
+ * @param {?WriteParams} [params]
633
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
634
+ */
635
+ public async cancelOwnershipHandoverRaw(params?: WriteParams) {
636
+ const { request, result } =
637
+ await simulateOwnableRolesCancelOwnershipHandover(this._config, {
638
+ address: this.assertValidAddress(),
639
+ ...this.optionallyAttachAccount(),
640
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
641
+ ...(params as any),
642
+ });
643
+ const hash = await writeOwnableRolesCancelOwnershipHandover(
644
+ this._config,
645
+ request,
646
+ );
647
+ return { hash, result };
648
+ }
649
+
650
+ /**
651
+ * Get the expiry timestamp for a pending ownership handover
652
+ * Returns 0 if there is no pending handover request for the given address
653
+ *
654
+ * @public
655
+ * @param {Address} pendingOwner - The address to check for pending handover requests
656
+ * @param {?ReadParams} [params]
657
+ * @returns {Promise<bigint>} - The timestamp when the handover request expires, or 0 if no request exists
658
+ */
659
+ public ownershipHandoverExpiresAt(
660
+ pendingOwner: Address,
661
+ params?: ReadParams,
662
+ ): Promise<bigint> {
663
+ return readOwnableRolesOwnershipHandoverExpiresAt(this._config, {
664
+ address: this.assertValidAddress(),
665
+ args: [pendingOwner],
666
+ ...this.optionallyAttachAccount(),
667
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
668
+ ...(params as any),
669
+ });
670
+ }
671
+
672
+ /**
673
+ * Allow the caller to remove their own roles
674
+ * If the caller does not have a role, then it will be a no-op for that role
675
+ *
676
+ * @public
677
+ * @async
678
+ * @param {Roles} roles - Bitmap of roles to renounce
679
+ * @param {?WriteParams} [params]
680
+ * @returns {Promise<void>}
681
+ */
682
+ public async renounceRoles(roles: Roles, params?: WriteParams) {
683
+ return await this.awaitResult(this.renounceRolesRaw(roles, params));
684
+ }
685
+
686
+ /**
687
+ * Allow the caller to remove their own roles
688
+ * If the caller does not have a role, then it will be a no-op for that role
689
+ *
690
+ * @public
691
+ * @async
692
+ * @param {Roles} roles - Bitmap of roles to renounce
693
+ * @param {?WriteParams} [params]
694
+ * @returns {Promise<{ hash: `0x${string}`; result: void; }>}
695
+ */
696
+ public async renounceRolesRaw(roles: Roles, params?: WriteParams) {
697
+ const { request, result } = await simulateOwnableRolesRenounceRoles(
698
+ this._config,
699
+ {
700
+ address: this.assertValidAddress(),
701
+ args: [roles],
702
+ ...this.optionallyAttachAccount(),
703
+ // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
704
+ ...(params as any),
705
+ },
706
+ );
707
+ const hash = await writeOwnableRolesRenounceRoles(this._config, request);
708
+ return { hash, result };
709
+ }
429
710
  }
@@ -37,6 +37,7 @@ describe("AllowListIncentive", () => {
37
37
  reward: 1n,
38
38
  limit: 1n,
39
39
  });
40
+ // @ts-expect-error
40
41
  await action.deploy();
41
42
  expect(isAddress(action.assertValidAddress())).toBe(true);
42
43
  });
@@ -186,4 +187,51 @@ describe("AllowListIncentive", () => {
186
187
  expect(e).toBeInstanceOf(Error);
187
188
  }
188
189
  });
190
+
191
+ test("isClaimable returns expected values", async () => {
192
+ const referrer = accounts[1].account;
193
+ const trustedSigner = accounts[0];
194
+ const allowList = await loadFixture(freshAllowList(fixtures));
195
+ const allowListIncentive = new fixtures.bases.AllowListIncentive(
196
+ defaultOptions,
197
+ {
198
+ allowList: allowList.assertValidAddress(),
199
+ limit: 1n,
200
+ },
201
+ );
202
+ const boost = await freshBoost(fixtures, {
203
+ incentives: [allowListIncentive],
204
+ });
205
+ await allowList.grantManyRoles(
206
+ [allowListIncentive.assertValidAddress()],
207
+ [Roles.MANAGER],
208
+ );
209
+
210
+ const claimant = trustedSigner.account;
211
+ const incentiveData = allowListIncentive.buildClaimData();
212
+
213
+ // Should be claimable before claiming
214
+ expect(await boost.incentives[0]!.isClaimable({ target: claimant })).toBe(true);
215
+
216
+ const claimDataPayload = await boost.validator.encodeClaimData({
217
+ signer: trustedSigner,
218
+ incentiveData,
219
+ chainId: defaultOptions.config.chains[0].id,
220
+ incentiveQuantity: boost.incentives.length,
221
+ claimant,
222
+ boostId: boost.id,
223
+ });
224
+
225
+ // Claim the incentive
226
+ await fixtures.core.claimIncentive(
227
+ boost.id,
228
+ 0n,
229
+ referrer,
230
+ claimDataPayload,
231
+ { value: parseEther("0.000075"), account: trustedSigner.privateKey },
232
+ );
233
+
234
+ // Should not be claimable after claiming
235
+ expect(await boost.incentives[0]!.isClaimable({ target: claimant })).toBe(false);
236
+ });
189
237
  });
@@ -272,7 +272,7 @@ export class AllowListIncentive extends DeployableTarget<
272
272
  ) {
273
273
  return await readAllowListIncentiveIsClaimable(this._config, {
274
274
  address: this.assertValidAddress(),
275
- args: [prepareClaimPayload(payload)],
275
+ args: [payload.target, '0x'],
276
276
  // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
277
277
  ...(params as any),
278
278
  });
@@ -33,6 +33,7 @@ describe("CGDAIncentive", () => {
33
33
  rewardDecay: 1n,
34
34
  manager: budgets.budget.address || zeroAddress,
35
35
  });
36
+ // @ts-expect-error
36
37
  await action.deploy();
37
38
  expect(isAddress(action.assertValidAddress())).toBe(true);
38
39
  });
@@ -208,4 +209,53 @@ describe("CGDAIncentive", () => {
208
209
  true,
209
210
  );
210
211
  });
212
+
213
+ test("isClaimable returns expected values", async () => {
214
+ const referrer = accounts[1].account;
215
+ const trustedSigner = accounts[0];
216
+ const cgdaIncentive = fixtures.core.CGDAIncentive({
217
+ asset: budgets.erc20.assertValidAddress(),
218
+ initialReward: 1n,
219
+ totalBudget: 10n,
220
+ rewardBoost: 1n,
221
+ rewardDecay: 1n,
222
+ manager: budgets.budget.assertValidAddress(),
223
+ });
224
+ const boost = await freshBoost(fixtures, {
225
+ budget: budgets.budget,
226
+ incentives: [cgdaIncentive],
227
+ });
228
+
229
+ const claimant = trustedSigner.account;
230
+ const incentiveData = cgdaIncentive.buildClaimData();
231
+
232
+ // Should be claimable before claiming
233
+ expect(await boost.incentives[0]!.isClaimable({
234
+ target: claimant,
235
+ data: incentiveData
236
+ })).toBe(true);
237
+
238
+ const claimDataPayload = await boost.validator.encodeClaimData({
239
+ signer: trustedSigner,
240
+ incentiveData,
241
+ chainId: defaultOptions.config.chains[0].id,
242
+ incentiveQuantity: boost.incentives.length,
243
+ claimant,
244
+ boostId: boost.id,
245
+ });
246
+
247
+ // Claim the incentive
248
+ await fixtures.core.claimIncentive(
249
+ boost.id,
250
+ 0n,
251
+ referrer,
252
+ claimDataPayload,
253
+ );
254
+
255
+ // Should not be claimable after claiming
256
+ expect(await boost.incentives[0]!.isClaimable({
257
+ target: claimant,
258
+ data: incentiveData
259
+ })).toBe(false);
260
+ });
211
261
  });
@@ -375,7 +375,7 @@ export class CGDAIncentive extends DeployableTarget<
375
375
  public async isClaimable(payload: ClaimPayload, params?: ReadParams) {
376
376
  return await readCgdaIncentiveIsClaimable(this._config, {
377
377
  address: this.assertValidAddress(),
378
- args: [prepareClaimPayload(payload)],
378
+ args: [payload.target, payload.data],
379
379
  // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
380
380
  ...(params as any),
381
381
  });
@@ -359,7 +359,7 @@ export class ERC1155Incentive extends DeployableTarget<
359
359
  public async isClaimable(payload: ClaimPayload, params?: ReadParams) {
360
360
  return await readErc1155IncentiveIsClaimable(this._config, {
361
361
  address: this.assertValidAddress(),
362
- args: [prepareClaimPayload(payload)],
362
+ args: [payload.target, payload.data],
363
363
  // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
364
364
  ...(params as any),
365
365
  });
@@ -39,6 +39,7 @@ describe("ERC20Incentive", () => {
39
39
  limit: 1n,
40
40
  manager: zeroAddress,
41
41
  });
42
+ // @ts-expect-error
42
43
  await action.deploy();
43
44
  expect(isAddress(action.assertValidAddress())).toBe(true);
44
45
  });
@@ -205,4 +206,52 @@ describe("ERC20Incentive", () => {
205
206
  true,
206
207
  );
207
208
  });
209
+
210
+ test("isClaimable returns expected values", async () => {
211
+ const referrer = accounts[1].account;
212
+ const trustedSigner = accounts[0];
213
+ const erc20Incentive = fixtures.core.ERC20Incentive({
214
+ asset: budgets.erc20.assertValidAddress(),
215
+ strategy: StrategyType.POOL,
216
+ reward: 1n,
217
+ limit: 1n,
218
+ manager: budgets.budget.assertValidAddress(),
219
+ });
220
+ const boost = await freshBoost(fixtures, {
221
+ budget: budgets.budget,
222
+ incentives: [erc20Incentive],
223
+ });
224
+
225
+ const claimant = trustedSigner.account;
226
+ const incentiveData = erc20Incentive.buildClaimData();
227
+
228
+ // Should be claimable before claiming
229
+ expect(await boost.incentives[0]!.isClaimable({
230
+ target: claimant,
231
+ data: incentiveData
232
+ })).toBe(true);
233
+
234
+ const claimDataPayload = await boost.validator.encodeClaimData({
235
+ signer: trustedSigner,
236
+ incentiveData,
237
+ chainId: defaultOptions.config.chains[0].id,
238
+ incentiveQuantity: boost.incentives.length,
239
+ claimant,
240
+ boostId: boost.id,
241
+ });
242
+
243
+ // Claim the incentive
244
+ await fixtures.core.claimIncentive(
245
+ boost.id,
246
+ 0n,
247
+ referrer,
248
+ claimDataPayload,
249
+ );
250
+
251
+ // Should not be claimable after claiming
252
+ expect(await boost.incentives[0]!.isClaimable({
253
+ target: claimant,
254
+ data: incentiveData
255
+ })).toBe(false);
256
+ });
208
257
  });