Incentive Integration Guide
Step-by-step walkthrough for projects that want to plug LiquidLink scoreboards into their own Move contracts and frontend.
Why integrate?
LiquidLink already ships the core liquidlink_incenctive_system package plus an integration example (iota/integrate_example). By following this guide a project can:
- obtain
ProjectCap/PointCapsafely from the protocol - deploy a periphery module that borrows the cap without exposing it
- wire a frontend against the
liquidlink-sdkor raw Move calls for user dashboards
Everything below mirrors the repo layout so you can copy/paste commands directly.
Prerequisites
- Tooling: Install the IOTA Move CLI (or Sui CLI if you target Sui) and make sure it can publish packages and call entry functions.
- Admin contact: You do not deploy the core package yourself. Instead, contact LiquidLink so they can issue a
ProjectCap+PointCapfrom the canonical deployment. (For local testing you can still run the Move tests withtest_scenario.) - Source tree: The integration examples live under
iota/integrate_example:periphery– Move module that storesPointCapinside a shared object and exposes helpers such asaward_points.frontend– Next.js +@iota/dapp-kitdemo UI.
1. Request project capabilities
LiquidLink operates the production liquidlink_incenctive_system deployment and holds the ProtocolAdminCap. Project teams request an allocation by submitting the address that should custody the caps. Internally the LiquidLink admin runs:
iota client call --package <CORE_PACKAGE_ID> \
--module router \
--function create_incentive_system \
--args <PROTOCOL_ADMIN_CAP_ID> <PROJECT_OWNER_ADDRESS> \
--gas-budget 200000000You will then receive:
ProjectCap– used once per scoreboard creation.PointCap– used for every scoring mutation; keep it offline until wrapped into aCapStore.
If a cap is compromised, notify LiquidLink immediately so we can revoke/replace it.
2. Create a shared scoreboard
iota client call --package <CORE_PACKAGE_ID> \
--module router \
--function entry_create_scoreboard \
--args <PROJECT_CAP_ID> <kind> \
--gas-budget 200000000kind = 0 produces a simple add/sub scoreboard. kind = 1 enables linear-time accumulation (requires providing a Clock later). Record the returned shared-object ID—this becomes your per-project scoreboard.
3. Deploy the periphery helper
The iota/integrate_example/periphery module demonstrates best practices:
cd ../integrate_example/periphery
iota move build
iota client publish --gas-budget 500000000Publishing gives you a PERIPHERY_PACKAGE_ID plus a custom YourAdminCap. The module exposes:
new_cap_store(admin_cap, point_cap)– stores the sensitivePointCapinside a sharedCapStore, preventing arbitrary transfers.award_points(cap_store, scoreboard, amount)/claw_back_points(...)– wrappers that borrow the cap and route torouter::add_point/router::subtract_point.reward_purchase(cap_store, scoreboard, purchase_value)– shows how to map business logic (e.g.,POINTS_PER_PURCHASE_UNIT) toaward_points.start_linear_reward/update_linear_reward– convenience wrappers for linear scoreboards; you passClockand new parameters, the helper ensures capability matching.
Store the PointCap
iota client call --package <PERIPHERY_PACKAGE_ID> \
--module periphery \
--function new_cap_store \
--args <YOUR_ADMIN_CAP_ID> <POINT_CAP_ID> \
--gas-budget 100000000Keep the returned CapStore object ID; workers or other contracts can now reference CapStore + Scoreboard without touching the raw capability.
Common transactions
# arbitrary award
iota client call --package <PERIPHERY_PACKAGE_ID> \
--module periphery \
--function award_points \
--args <CAP_STORE_ID> <SCOREBOARD_ID> 50 \
--gas-budget 100000000
# purchase multiplier demo (POINTS_PER_PURCHASE_UNIT = 10)
iota client call --package <PERIPHERY_PACKAGE_ID> \
--module periphery \
--function reward_purchase \
--args <CAP_STORE_ID> <SCOREBOARD_ID> 3 \
--gas-budget 100000000
# claw back for refunds
iota client call --package <PERIPHERY_PACKAGE_ID> \
--module periphery \
--function claw_back_points \
--args <CAP_STORE_ID> <SCOREBOARD_ID> 5 \
--gas-budget 100000000
# linear reward (kind must be 1, Clock address depends on chain)
iota client call --package <PERIPHERY_PACKAGE_ID> \
--module periphery \
--function start_linear_reward \
--args <CAP_STORE_ID> <LINEAR_SCOREBOARD_ID> 100 60000 0x6 \
--gas-budget 2000000004. Frontend wiring
Under iota/integrate_example/frontend you’ll find a Next.js app configured with:
@iota/dapp-kitfor wallet connection- React Query for data fetching
- Example hooks that call the
liquidlink-sdkclient
Focus on:
components– shared UI blocks for displaying scores.hooks/useScoreboard.ts– fetcher that callsClient.getScoreInfo/getComputedScore.lib/network.ts– holds the scoreboard IDs and package metadata you recorded earlier.
Run it with:
cd ../frontend
bun install
bun devUpdate TARGET_ADDRESS, scoreboard IDs, and package IDs to match your deployment.
5. Testing strategy
- Unit tests:
iota/liquidlink_incenctive_system/tests/liquidlink_incenctive_system_tests.movecovers router flows, linear logic, and cap matching. Runiota move test. - Periphery tests: Add Move tests under
integrate_example/periphery/teststo mirror your custom business logic. - End-to-end: After storing a
PointCapin theCapStore, simulate award/claw-back transactions and verify events vialiquidlink-sdk(getEventsByAddress) or the chain explorer.
Checklist
- Project team received
ProjectCap+PointCapfrom LiquidLink admin. - Shared
Scoreboardcreated and ID documented. -
PointCapstored inCapStore. - Frontend configured with correct IDs + RPC endpoints.
Once all boxes are checked your project can award points, claw them back, run linear-time campaigns, and display user balances confidently.