scheduler: use token API when destroying objects
diff --git a/sdk/core/scheduler/common.h b/sdk/core/scheduler/common.h
index ea9ccfd..0c43380 100644
--- a/sdk/core/scheduler/common.h
+++ b/sdk/core/scheduler/common.h
@@ -51,10 +51,17 @@
 	 * Subclasses must implement a static `sealing_type` method that returns
 	 * the sealing key.
 	 */
-	template<bool IsDynamic>
+	template<bool IsDynamicArg>
 	struct Handle
 	{
 		/**
+		 * Some Handle types must reside in static memory, and some may be built
+		 * on the fly, and in particular in the shared heap, at runtime.  Is
+		 * this type among the latter?
+		 */
+		static constexpr bool IsDynamic = IsDynamicArg;
+
+		/**
 		 * Unseal `unsafePointer` as a pointer to an object of the specified
 		 * type.  Returns nullptr if `unsafePointer` is not a valid sealed
 		 * pointer to an object of the correct type.
diff --git a/sdk/core/scheduler/main.cc b/sdk/core/scheduler/main.cc
index d5d2b01..d4d36a8 100644
--- a/sdk/core/scheduler/main.cc
+++ b/sdk/core/scheduler/main.cc
@@ -375,20 +375,24 @@
 
 	/// Helper to safely deallocate an instance of `T`.
 	template<typename T>
-	int deallocate(SObjStruct *heapCapability, void *object)
+	int deallocate(SObjStruct *heapCapability, void *objectPtr)
 	{
+		static_assert(T::IsDynamic);
+
 		// Acquire the lock and hold it. We need to be careful of two attempts
 		// to free the same object racing, so we cause others to back up behind
 		// this one.  They will then fail in the unseal operation.
 		LockGuard g{deallocLock};
-		return typed_op<T>(object, [&](T &unsealed) {
-			if (int ret = heap_can_free(heapCapability, &unsealed); ret != 0)
+		return typed_op<T>(objectPtr, [&](T &unsealed) {
+			SObj object = static_cast<SObj>(objectPtr);
+			if (int ret = token_obj_can_destroy(
+			      heapCapability, T::sealing_type(), object);
+			    ret != 0)
 			{
 				return ret;
 			}
 			unsealed.~T();
-			heap_free(heapCapability, &unsealed);
-			return 0;
+			return token_obj_destroy(heapCapability, T::sealing_type(), object);
 		});
 	}