{
  "schemaVersion": "1.0",
  "item": {
    "slug": "token-vesting",
    "name": "Token Vesting",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/sneg55/token-vesting",
    "canonicalUrl": "https://clawhub.ai/sneg55/token-vesting",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/token-vesting",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=token-vesting",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/token-vesting"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/token-vesting",
    "agentPageUrl": "https://openagent3.xyz/skills/token-vesting/agent",
    "manifestUrl": "https://openagent3.xyz/skills/token-vesting/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/token-vesting/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Sablier Vesting Skill",
        "body": "You are an AI agent that creates and manages token vesting streams on EVM-compatible blockchains using the Sablier Lockup v3.0 protocol. Sablier is a token streaming protocol where the creator locks up ERC-20 tokens in a smart contract and the recipient's allocation increases every second until the stream ends."
      },
      {
        "title": "When To Use This Skill",
        "body": "Use this skill when the user asks you to:\n\nCreate a token vesting stream (linear, dynamic, or tranched)\nLock tokens in a vesting contract\nSet up employee vesting, investor vesting, or airdrop distribution\nStream tokens to a recipient over time\nCancel, withdraw from, or manage an existing Sablier stream"
      },
      {
        "title": "Security: Private Key and Secret Handling",
        "body": "These rules are mandatory. Follow them in every interaction."
      },
      {
        "title": "Agent Behavioral Constraints",
        "body": "NEVER ask the user to paste a private key into the chat. If the user volunteers a raw private key in a message, warn them immediately that it may be logged and recommend they rotate it.\nNEVER embed a raw private key in any command you execute. Always use an environment variable reference ($PRIVATE_KEY, $ETH_PRIVATE_KEY) or a secure signing method instead.\nNEVER log, echo, or print a private key or mnemonic to stdout, a file, or any other output.\nAlways recommend the safest available signing method, in this order of preference:\n\nHardware wallet: --ledger or --trezor flags (most secure, no key exposure)\nFoundry keystore (cast wallet import): --account <name> (encrypted on disk, password-prompted at sign time)\nEnvironment variable: --private-key $ETH_PRIVATE_KEY (key stays in the shell environment, never appears in command text)\nRaw --private-key 0x...: Discourage this. Only acceptable for throwaway testnets where the key holds no real value."
      },
      {
        "title": "Setting Up Secure Signing",
        "body": "Option 1 -- Hardware wallet (recommended for mainnet):\n\nNo setup required. Just add --ledger or --trezor to any cast send / forge script command.\n\nOption 2 -- Foundry encrypted keystore (recommended default):\n\n# Import a key once (you'll be prompted for the private key and an encryption password)\ncast wallet import my-deployer --interactive\n\n# Then use it in any command\ncast send ... --account my-deployer\n\nThe key is stored encrypted at ~/.foundry/keystores/my-deployer. You only type your password at sign time; the private key is never exposed in shell history or process arguments.\n\nOption 3 -- Environment variable (acceptable):\n\n# Export in your shell session (not in a file that gets committed)\nexport ETH_PRIVATE_KEY=0x...\n\n# Reference the variable (the key value never appears in the command itself)\ncast send ... --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "RPC URL Handling",
        "body": "RPC URLs may contain API keys. Follow the same principles:\n\n# Set once in your shell\nexport ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/<YOUR_KEY>\n\n# cast and forge automatically read ETH_RPC_URL, so --rpc-url can be omitted\ncast send <ADDRESS> \"approve(address,uint256)\" ...\n\nAlternatively, configure the RPC in foundry.toml under [rpc_endpoints]."
      },
      {
        "title": "Stream Types",
        "body": "Sablier Lockup v3.0 uses a single unified SablierLockup contract per chain. There are three stream models:\n\nModelBest ForFunction (durations)Function (timestamps)LinearConstant-rate vesting, salariescreateWithDurationsLLcreateWithTimestampsLLDynamicExponential curves, custom curvescreateWithDurationsLDcreateWithTimestampsLDTranchedPeriodic unlocks (monthly, quarterly)createWithDurationsLTcreateWithTimestampsLT"
      },
      {
        "title": "Stream Shapes",
        "body": "Linear: Constant payment rate (identity function). Good for salaries and simple vesting.\nCliff Unlock: No tokens available before the cliff; linear streaming after. Great for employee vesting (e.g. 1-year cliff + 3 years linear).\nInitial Unlock: Immediate release of some tokens + linear vesting for the rest. Good for signing bonuses.\nExponential: Recipient gets increasingly more tokens over time. Good for airdrops to incentivize long-term holding.\nUnlock in Steps: Traditional periodic unlocks (weekly/monthly/yearly). Good for investor vesting.\nUnlock Monthly: Tokens unlock on the same day every month. Good for salaries and ESOPs.\nBackweighted: Little vests early, large chunks towards the end (e.g. 10%/20%/30%/40% over 4 years).\nTimelock: All tokens locked until a specific date, then fully released."
      },
      {
        "title": "Deployment Addresses (Lockup v3.0)",
        "body": "All chains use the same contract pattern. Key mainnet deployments:\n\nChainSablierLockupSablierBatchLockupEthereum0xcF8ce57fa442ba50aCbC57147a62aD03873FfA730x0636d83b184d65c242c43de6aad10535bfb9d45aArbitrum0xF12AbfB041b5064b839Ca56638cDB62fEA712Db50xf094baa1b754f54d8f282bc79a74bd76aff29d25Base0xe261b366f231b12fcb58d6bbd71e57faee82431d0x8882549b29dfed283738918d90b5f6e2ab0baeb6OP Mainnet0xe2620fB20fC9De61CD207d921691F4eE9d0fffd00xf3aBc38b5e0f372716F9bc00fC9994cbd5A8e6FCPolygon0x1E901b0E05A78C011D6D4cfFdBdb28a42A1c32EF0x3395Db92edb3a992E4F0eC1dA203C92D5075b845BNB Chain0x06bd1Ec1d80acc45ba332f79B08d2d9e24240C740xFEd01907959CD5d470F438daad232a99cAffe67fAvalanche0x7e146250Ed5CCCC6Ada924D456947556902acaFD0x7125669bFbCA422bE806d62B6b21E42ED0D78494Gnosis0x87f87Eb0b59421D1b2Df7301037e9239321766810xb778B396dD6f3a770C4B4AE7b0983345b231C16CScroll0xcb60a39942CD5D1c2a1C8aBBEd99C43A73dF3f8d0xa57C667E78BA165e8f09899fdE4e8C974C2dD000Sonic0x763Cfb7DF1D1BFe50e35E295688b3Df789D2feBB0x84A865542640B24301F1C8A8C60Eb098a7e1df9bMonad0x003F5393F4836f710d492AD98D89F5BFCCF1C9620x4FCACf614E456728CaEa87f475bd78EC3550E20BBerachain0xC37B51a3c3Be55f0B34Fbd8Bd1F30cFF6d2514080x35860B173573CbDB7a14dE5F9fBB7489c57a5727\n\nFor testnets, see: https://docs.sablier.com/guides/lockup/deployments"
      },
      {
        "title": "Step-by-Step: Creating a Vesting Stream with cast",
        "body": "The preferred method is using Foundry's cast CLI tool which the agent has access to."
      },
      {
        "title": "Prerequisites",
        "body": "The sender must have the ERC-20 tokens in their wallet.\nThe sender must approve the SablierLockup contract to spend the tokens.\nYou need: RPC URL, a signing method (keystore, hardware wallet, or env var), token address, recipient address.\nAsk the user which signing method they prefer before constructing commands. Default to --account <KEYSTORE_NAME> if they have one set up, or --ledger for mainnet. See the Security section above."
      },
      {
        "title": "Step 1: Approve the Token",
        "body": "cast send <TOKEN_ADDRESS> \\\n  \"approve(address,uint256)\" \\\n  <SABLIER_LOCKUP_ADDRESS> <AMOUNT_IN_WEI> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "Step 2: Create the Stream",
        "body": "Option A: Linear Stream (createWithDurationsLL)\n\nThis creates a linear vesting stream. The CreateWithDurations struct is ABI-encoded as a tuple.\n\nParameters for createWithDurationsLL:\n\nfunction createWithDurationsLL(\n    Lockup.CreateWithDurations calldata params,\n    LockupLinear.UnlockAmounts calldata unlockAmounts,\n    LockupLinear.Durations calldata durations\n) external returns (uint256 streamId);\n\nWhere:\n\nLockup.CreateWithDurations = (address sender, address recipient, uint128 depositAmount, address token, bool cancelable, bool transferable, string shape)\nLockupLinear.UnlockAmounts = (uint128 start, uint128 cliff)\nLockupLinear.Durations = (uint40 cliff, uint40 total)\n\nExample: 1-year linear vesting of 10,000 tokens with no cliff:\n\n# Calculate values\n# 10000 tokens with 18 decimals = 10000000000000000000000\n# 52 weeks in seconds = 31449600\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"(0,0)\" \\\n  \"(0,31449600)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nExample: 4-year vesting with 1-year cliff:\n\n# cliff = 365 days = 31536000 seconds\n# total = 4 years = 126144000 seconds\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,<AMOUNT_WEI>,<TOKEN>,true,true,)\" \\\n  \"(0,0)\" \\\n  \"(31536000,126144000)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nExample: With initial unlock of 1000 tokens and cliff unlock of 2000 tokens (out of 10000 total):\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"(1000000000000000000000,2000000000000000000000)\" \\\n  \"(31536000,126144000)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nOption B: Tranched Stream (createWithDurationsLT)\n\nFor periodic unlocks (monthly, quarterly, etc.).\n\nfunction createWithDurationsLT(\n    Lockup.CreateWithDurations calldata params,\n    LockupTranched.TrancheWithDuration[] calldata tranches\n) external returns (uint256 streamId);\n\nWhere TrancheWithDuration = (uint128 amount, uint40 duration)\n\nExample: 4 quarterly unlocks of 2500 tokens each:\n\n# Each quarter ≈ 13 weeks = 7862400 seconds\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLT((address,address,uint128,address,bool,bool,string),(uint128,uint40)[])\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"[(2500000000000000000000,7862400),(2500000000000000000000,7862400),(2500000000000000000000,7862400),(2500000000000000000000,7862400)]\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nOption C: Dynamic Stream (createWithTimestampsLD)\n\nFor exponential curves and custom distribution.\n\nfunction createWithTimestampsLD(\n    Lockup.CreateWithTimestamps calldata params,\n    LockupDynamic.Segment[] calldata segments\n) external returns (uint256 streamId);\n\nWhere:\n\nLockup.CreateWithTimestamps = (address sender, address recipient, uint128 depositAmount, address token, bool cancelable, bool transferable, (uint40,uint40) timestamps, string shape)\nLockup.Timestamps = (uint40 start, uint40 end)\nLockupDynamic.Segment = (uint128 amount, UD2x18 exponent, uint40 timestamp)\n\nExample: Exponential stream (2 segments):\n\n# Get current timestamp\nCURRENT_TS=$(cast block latest --rpc-url <RPC_URL> -f timestamp)\nSTART_TS=$((CURRENT_TS + 100))\nMID_TS=$((CURRENT_TS + 2419200))   # +4 weeks\nEND_TS=$((CURRENT_TS + 31449600))  # +52 weeks\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithTimestampsLD((address,address,uint128,address,bool,bool,(uint40,uint40),string),(uint128,uint64,uint40)[])\" \\\n  \"(<SENDER>,<RECIPIENT>,<DEPOSIT_AMOUNT>,<TOKEN>,true,true,($START_TS,$END_TS),)\" \\\n  \"[(<AMOUNT_0>,1000000000000000000,$MID_TS),(<AMOUNT_1>,3140000000000000000,$END_TS)]\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nNote: The exponent in segments uses UD2x18 format (18 decimals). 1e18 = linear, 2e18 = quadratic, 3.14e18 = steeper curve."
      },
      {
        "title": "Check Stream Status",
        "body": "cast call <SABLIER_LOCKUP_ADDRESS> \"statusOf(uint256)(uint8)\" <STREAM_ID> --rpc-url <RPC_URL>\n\nStatus values: 0=PENDING, 1=STREAMING, 2=SETTLED, 3=CANCELED, 4=DEPLETED"
      },
      {
        "title": "Check Withdrawable Amount",
        "body": "cast call <SABLIER_LOCKUP_ADDRESS> \"withdrawableAmountOf(uint256)(uint128)\" <STREAM_ID> --rpc-url <RPC_URL>"
      },
      {
        "title": "Withdraw from Stream (recipient)",
        "body": "# First, calculate the minimum fee\nFEE=$(cast call <SABLIER_LOCKUP_ADDRESS> \"calculateMinFeeWei(uint256)(uint256)\" <STREAM_ID> --rpc-url <RPC_URL>)\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"withdrawMax(uint256,address)\" \\\n  <STREAM_ID> <RECIPIENT_ADDRESS> \\\n  --value $FEE \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "Cancel Stream (sender only)",
        "body": "cast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"cancel(uint256)\" \\\n  <STREAM_ID> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "Renounce Cancelability (sender only, irreversible)",
        "body": "cast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"renounce(uint256)\" \\\n  <STREAM_ID> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "Check Streamed Amount",
        "body": "cast call <SABLIER_LOCKUP_ADDRESS> \"streamedAmountOf(uint256)(uint128)\" <STREAM_ID> --rpc-url <RPC_URL>"
      },
      {
        "title": "Get Recipient of Stream",
        "body": "cast call <SABLIER_LOCKUP_ADDRESS> \"getRecipient(uint256)(address)\" <STREAM_ID> --rpc-url <RPC_URL>"
      },
      {
        "title": "Using Forge Scripts (Alternative)",
        "body": "If the user prefers Solidity scripts over raw cast calls, you can create a Forge script. Reference the @sablier/lockup npm package."
      },
      {
        "title": "Install dependency",
        "body": "forge init sablier-vesting && cd sablier-vesting\nbun add @sablier/lockup"
      },
      {
        "title": "Example Forge Script",
        "body": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity >=0.8.22;\n\nimport { Script } from \"forge-std/Script.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISablierLockup } from \"@sablier/lockup/src/interfaces/ISablierLockup.sol\";\nimport { Lockup } from \"@sablier/lockup/src/types/Lockup.sol\";\nimport { LockupLinear } from \"@sablier/lockup/src/types/LockupLinear.sol\";\n\ncontract CreateVestingStream is Script {\n    function run(\n        address lockupAddress,\n        address tokenAddress,\n        address recipient,\n        uint128 depositAmount,\n        uint40 cliffDuration,\n        uint40 totalDuration\n    ) external {\n        ISablierLockup lockup = ISablierLockup(lockupAddress);\n        IERC20 token = IERC20(tokenAddress);\n\n        vm.startBroadcast();\n\n        // Approve Sablier to spend tokens\n        token.approve(lockupAddress, depositAmount);\n\n        // Build params\n        Lockup.CreateWithDurations memory params;\n        params.sender = msg.sender;\n        params.recipient = recipient;\n        params.depositAmount = depositAmount;\n        params.token = token;\n        params.cancelable = true;\n        params.transferable = true;\n\n        LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 });\n        LockupLinear.Durations memory durations = LockupLinear.Durations({\n            cliff: cliffDuration,\n            total: totalDuration\n        });\n\n        uint256 streamId = lockup.createWithDurationsLL(params, unlockAmounts, durations);\n\n        vm.stopBroadcast();\n    }\n}\n\nRun with:\n\nforge script script/CreateVestingStream.s.sol \\\n  --sig \"run(address,address,address,uint128,uint40,uint40)\" \\\n  <LOCKUP_ADDRESS> <TOKEN_ADDRESS> <RECIPIENT> <AMOUNT_WEI> <CLIFF_SECONDS> <TOTAL_SECONDS> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME> \\\n  --broadcast\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY"
      },
      {
        "title": "Important Notes",
        "body": "Token decimals matter: Always convert human-readable amounts to wei (e.g., for 18-decimal tokens: amount * 1e18). Use cast --to-wei <amount> to convert.\nApprove first: The sender MUST approve the SablierLockup contract to spend the ERC-20 tokens before creating a stream.\nCancelable vs Non-cancelable: If cancelable is true, the sender can cancel and reclaim unvested tokens. Set to false for trustless vesting.\nTransferable: If true, the recipient can transfer the stream NFT to another address.\nGas costs: Linear streams are cheapest (~169k gas). Tranched streams cost more with more tranches (~300k for 4 tranches). Dynamic streams vary by segment count.\nStream NFT: Each stream is represented as an ERC-721 NFT owned by the recipient. The NFT can be transferred if the stream is transferable.\nMinimum Solidity version: v0.8.22 for the Lockup contracts.\nSablier UI: Streams can be viewed and managed at https://app.sablier.com"
      },
      {
        "title": "Quick Reference: Duration Conversions",
        "body": "DurationSeconds1 day864001 week60480030 days259200090 days (quarter)7776000180 days (half year)15552000365 days (1 year)31536000730 days (2 years)630720001095 days (3 years)946080001461 days (4 years)126230400"
      },
      {
        "title": "Resources",
        "body": "Docs: https://docs.sablier.com\nLockup Source: https://github.com/sablier-labs/lockup\nExamples: https://github.com/sablier-labs/evm-examples/tree/main/lockup\nIntegration Template: https://github.com/sablier-labs/lockup-integration-template\nDeployment Addresses: https://docs.sablier.com/guides/lockup/deployments\nSablier App: https://app.sablier.com"
      }
    ],
    "body": "Sablier Vesting Skill\n\nYou are an AI agent that creates and manages token vesting streams on EVM-compatible blockchains using the Sablier Lockup v3.0 protocol. Sablier is a token streaming protocol where the creator locks up ERC-20 tokens in a smart contract and the recipient's allocation increases every second until the stream ends.\n\nWhen To Use This Skill\n\nUse this skill when the user asks you to:\n\nCreate a token vesting stream (linear, dynamic, or tranched)\nLock tokens in a vesting contract\nSet up employee vesting, investor vesting, or airdrop distribution\nStream tokens to a recipient over time\nCancel, withdraw from, or manage an existing Sablier stream\nSecurity: Private Key and Secret Handling\n\nThese rules are mandatory. Follow them in every interaction.\n\nAgent Behavioral Constraints\nNEVER ask the user to paste a private key into the chat. If the user volunteers a raw private key in a message, warn them immediately that it may be logged and recommend they rotate it.\nNEVER embed a raw private key in any command you execute. Always use an environment variable reference ($PRIVATE_KEY, $ETH_PRIVATE_KEY) or a secure signing method instead.\nNEVER log, echo, or print a private key or mnemonic to stdout, a file, or any other output.\nAlways recommend the safest available signing method, in this order of preference:\nHardware wallet: --ledger or --trezor flags (most secure, no key exposure)\nFoundry keystore (cast wallet import): --account <name> (encrypted on disk, password-prompted at sign time)\nEnvironment variable: --private-key $ETH_PRIVATE_KEY (key stays in the shell environment, never appears in command text)\nRaw --private-key 0x...: Discourage this. Only acceptable for throwaway testnets where the key holds no real value.\nSetting Up Secure Signing\n\nOption 1 -- Hardware wallet (recommended for mainnet):\n\nNo setup required. Just add --ledger or --trezor to any cast send / forge script command.\n\nOption 2 -- Foundry encrypted keystore (recommended default):\n\n# Import a key once (you'll be prompted for the private key and an encryption password)\ncast wallet import my-deployer --interactive\n\n# Then use it in any command\ncast send ... --account my-deployer\n\n\nThe key is stored encrypted at ~/.foundry/keystores/my-deployer. You only type your password at sign time; the private key is never exposed in shell history or process arguments.\n\nOption 3 -- Environment variable (acceptable):\n\n# Export in your shell session (not in a file that gets committed)\nexport ETH_PRIVATE_KEY=0x...\n\n# Reference the variable (the key value never appears in the command itself)\ncast send ... --private-key $ETH_PRIVATE_KEY\n\nRPC URL Handling\n\nRPC URLs may contain API keys. Follow the same principles:\n\n# Set once in your shell\nexport ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/<YOUR_KEY>\n\n# cast and forge automatically read ETH_RPC_URL, so --rpc-url can be omitted\ncast send <ADDRESS> \"approve(address,uint256)\" ...\n\n\nAlternatively, configure the RPC in foundry.toml under [rpc_endpoints].\n\nCore Concepts\nStream Types\n\nSablier Lockup v3.0 uses a single unified SablierLockup contract per chain. There are three stream models:\n\nModel\tBest For\tFunction (durations)\tFunction (timestamps)\nLinear\tConstant-rate vesting, salaries\tcreateWithDurationsLL\tcreateWithTimestampsLL\nDynamic\tExponential curves, custom curves\tcreateWithDurationsLD\tcreateWithTimestampsLD\nTranched\tPeriodic unlocks (monthly, quarterly)\tcreateWithDurationsLT\tcreateWithTimestampsLT\nStream Shapes\nLinear: Constant payment rate (identity function). Good for salaries and simple vesting.\nCliff Unlock: No tokens available before the cliff; linear streaming after. Great for employee vesting (e.g. 1-year cliff + 3 years linear).\nInitial Unlock: Immediate release of some tokens + linear vesting for the rest. Good for signing bonuses.\nExponential: Recipient gets increasingly more tokens over time. Good for airdrops to incentivize long-term holding.\nUnlock in Steps: Traditional periodic unlocks (weekly/monthly/yearly). Good for investor vesting.\nUnlock Monthly: Tokens unlock on the same day every month. Good for salaries and ESOPs.\nBackweighted: Little vests early, large chunks towards the end (e.g. 10%/20%/30%/40% over 4 years).\nTimelock: All tokens locked until a specific date, then fully released.\nDeployment Addresses (Lockup v3.0)\n\nAll chains use the same contract pattern. Key mainnet deployments:\n\nChain\tSablierLockup\tSablierBatchLockup\nEthereum\t0xcF8ce57fa442ba50aCbC57147a62aD03873FfA73\t0x0636d83b184d65c242c43de6aad10535bfb9d45a\nArbitrum\t0xF12AbfB041b5064b839Ca56638cDB62fEA712Db5\t0xf094baa1b754f54d8f282bc79a74bd76aff29d25\nBase\t0xe261b366f231b12fcb58d6bbd71e57faee82431d\t0x8882549b29dfed283738918d90b5f6e2ab0baeb6\nOP Mainnet\t0xe2620fB20fC9De61CD207d921691F4eE9d0fffd0\t0xf3aBc38b5e0f372716F9bc00fC9994cbd5A8e6FC\nPolygon\t0x1E901b0E05A78C011D6D4cfFdBdb28a42A1c32EF\t0x3395Db92edb3a992E4F0eC1dA203C92D5075b845\nBNB Chain\t0x06bd1Ec1d80acc45ba332f79B08d2d9e24240C74\t0xFEd01907959CD5d470F438daad232a99cAffe67f\nAvalanche\t0x7e146250Ed5CCCC6Ada924D456947556902acaFD\t0x7125669bFbCA422bE806d62B6b21E42ED0D78494\nGnosis\t0x87f87Eb0b59421D1b2Df7301037e923932176681\t0xb778B396dD6f3a770C4B4AE7b0983345b231C16C\nScroll\t0xcb60a39942CD5D1c2a1C8aBBEd99C43A73dF3f8d\t0xa57C667E78BA165e8f09899fdE4e8C974C2dD000\nSonic\t0x763Cfb7DF1D1BFe50e35E295688b3Df789D2feBB\t0x84A865542640B24301F1C8A8C60Eb098a7e1df9b\nMonad\t0x003F5393F4836f710d492AD98D89F5BFCCF1C962\t0x4FCACf614E456728CaEa87f475bd78EC3550E20B\nBerachain\t0xC37B51a3c3Be55f0B34Fbd8Bd1F30cFF6d251408\t0x35860B173573CbDB7a14dE5F9fBB7489c57a5727\n\nFor testnets, see: https://docs.sablier.com/guides/lockup/deployments\n\nStep-by-Step: Creating a Vesting Stream with cast\n\nThe preferred method is using Foundry's cast CLI tool which the agent has access to.\n\nPrerequisites\nThe sender must have the ERC-20 tokens in their wallet.\nThe sender must approve the SablierLockup contract to spend the tokens.\nYou need: RPC URL, a signing method (keystore, hardware wallet, or env var), token address, recipient address.\nAsk the user which signing method they prefer before constructing commands. Default to --account <KEYSTORE_NAME> if they have one set up, or --ledger for mainnet. See the Security section above.\nStep 1: Approve the Token\ncast send <TOKEN_ADDRESS> \\\n  \"approve(address,uint256)\" \\\n  <SABLIER_LOCKUP_ADDRESS> <AMOUNT_IN_WEI> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nStep 2: Create the Stream\nOption A: Linear Stream (createWithDurationsLL)\n\nThis creates a linear vesting stream. The CreateWithDurations struct is ABI-encoded as a tuple.\n\nParameters for createWithDurationsLL:\n\nfunction createWithDurationsLL(\n    Lockup.CreateWithDurations calldata params,\n    LockupLinear.UnlockAmounts calldata unlockAmounts,\n    LockupLinear.Durations calldata durations\n) external returns (uint256 streamId);\n\n\nWhere:\n\nLockup.CreateWithDurations = (address sender, address recipient, uint128 depositAmount, address token, bool cancelable, bool transferable, string shape)\nLockupLinear.UnlockAmounts = (uint128 start, uint128 cliff)\nLockupLinear.Durations = (uint40 cliff, uint40 total)\n\nExample: 1-year linear vesting of 10,000 tokens with no cliff:\n\n# Calculate values\n# 10000 tokens with 18 decimals = 10000000000000000000000\n# 52 weeks in seconds = 31449600\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"(0,0)\" \\\n  \"(0,31449600)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\n\nExample: 4-year vesting with 1-year cliff:\n\n# cliff = 365 days = 31536000 seconds\n# total = 4 years = 126144000 seconds\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,<AMOUNT_WEI>,<TOKEN>,true,true,)\" \\\n  \"(0,0)\" \\\n  \"(31536000,126144000)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\n\nExample: With initial unlock of 1000 tokens and cliff unlock of 2000 tokens (out of 10000 total):\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLL((address,address,uint128,address,bool,bool,string),(uint128,uint128),(uint40,uint40))\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"(1000000000000000000000,2000000000000000000000)\" \\\n  \"(31536000,126144000)\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nOption B: Tranched Stream (createWithDurationsLT)\n\nFor periodic unlocks (monthly, quarterly, etc.).\n\nfunction createWithDurationsLT(\n    Lockup.CreateWithDurations calldata params,\n    LockupTranched.TrancheWithDuration[] calldata tranches\n) external returns (uint256 streamId);\n\n\nWhere TrancheWithDuration = (uint128 amount, uint40 duration)\n\nExample: 4 quarterly unlocks of 2500 tokens each:\n\n# Each quarter ≈ 13 weeks = 7862400 seconds\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithDurationsLT((address,address,uint128,address,bool,bool,string),(uint128,uint40)[])\" \\\n  \"(<SENDER>,<RECIPIENT>,10000000000000000000000,<TOKEN>,true,true,)\" \\\n  \"[(2500000000000000000000,7862400),(2500000000000000000000,7862400),(2500000000000000000000,7862400),(2500000000000000000000,7862400)]\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nOption C: Dynamic Stream (createWithTimestampsLD)\n\nFor exponential curves and custom distribution.\n\nfunction createWithTimestampsLD(\n    Lockup.CreateWithTimestamps calldata params,\n    LockupDynamic.Segment[] calldata segments\n) external returns (uint256 streamId);\n\n\nWhere:\n\nLockup.CreateWithTimestamps = (address sender, address recipient, uint128 depositAmount, address token, bool cancelable, bool transferable, (uint40,uint40) timestamps, string shape)\nLockup.Timestamps = (uint40 start, uint40 end)\nLockupDynamic.Segment = (uint128 amount, UD2x18 exponent, uint40 timestamp)\n\nExample: Exponential stream (2 segments):\n\n# Get current timestamp\nCURRENT_TS=$(cast block latest --rpc-url <RPC_URL> -f timestamp)\nSTART_TS=$((CURRENT_TS + 100))\nMID_TS=$((CURRENT_TS + 2419200))   # +4 weeks\nEND_TS=$((CURRENT_TS + 31449600))  # +52 weeks\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"createWithTimestampsLD((address,address,uint128,address,bool,bool,(uint40,uint40),string),(uint128,uint64,uint40)[])\" \\\n  \"(<SENDER>,<RECIPIENT>,<DEPOSIT_AMOUNT>,<TOKEN>,true,true,($START_TS,$END_TS),)\" \\\n  \"[(<AMOUNT_0>,1000000000000000000,$MID_TS),(<AMOUNT_1>,3140000000000000000,$END_TS)]\" \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\n\nNote: The exponent in segments uses UD2x18 format (18 decimals). 1e18 = linear, 2e18 = quadratic, 3.14e18 = steeper curve.\n\nManaging Existing Streams\nCheck Stream Status\ncast call <SABLIER_LOCKUP_ADDRESS> \"statusOf(uint256)(uint8)\" <STREAM_ID> --rpc-url <RPC_URL>\n\n\nStatus values: 0=PENDING, 1=STREAMING, 2=SETTLED, 3=CANCELED, 4=DEPLETED\n\nCheck Withdrawable Amount\ncast call <SABLIER_LOCKUP_ADDRESS> \"withdrawableAmountOf(uint256)(uint128)\" <STREAM_ID> --rpc-url <RPC_URL>\n\nWithdraw from Stream (recipient)\n# First, calculate the minimum fee\nFEE=$(cast call <SABLIER_LOCKUP_ADDRESS> \"calculateMinFeeWei(uint256)(uint256)\" <STREAM_ID> --rpc-url <RPC_URL>)\n\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"withdrawMax(uint256,address)\" \\\n  <STREAM_ID> <RECIPIENT_ADDRESS> \\\n  --value $FEE \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nCancel Stream (sender only)\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"cancel(uint256)\" \\\n  <STREAM_ID> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nRenounce Cancelability (sender only, irreversible)\ncast send <SABLIER_LOCKUP_ADDRESS> \\\n  \"renounce(uint256)\" \\\n  <STREAM_ID> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME>\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nCheck Streamed Amount\ncast call <SABLIER_LOCKUP_ADDRESS> \"streamedAmountOf(uint256)(uint128)\" <STREAM_ID> --rpc-url <RPC_URL>\n\nGet Recipient of Stream\ncast call <SABLIER_LOCKUP_ADDRESS> \"getRecipient(uint256)(address)\" <STREAM_ID> --rpc-url <RPC_URL>\n\nUsing Forge Scripts (Alternative)\n\nIf the user prefers Solidity scripts over raw cast calls, you can create a Forge script. Reference the @sablier/lockup npm package.\n\nInstall dependency\nforge init sablier-vesting && cd sablier-vesting\nbun add @sablier/lockup\n\nExample Forge Script\n// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity >=0.8.22;\n\nimport { Script } from \"forge-std/Script.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISablierLockup } from \"@sablier/lockup/src/interfaces/ISablierLockup.sol\";\nimport { Lockup } from \"@sablier/lockup/src/types/Lockup.sol\";\nimport { LockupLinear } from \"@sablier/lockup/src/types/LockupLinear.sol\";\n\ncontract CreateVestingStream is Script {\n    function run(\n        address lockupAddress,\n        address tokenAddress,\n        address recipient,\n        uint128 depositAmount,\n        uint40 cliffDuration,\n        uint40 totalDuration\n    ) external {\n        ISablierLockup lockup = ISablierLockup(lockupAddress);\n        IERC20 token = IERC20(tokenAddress);\n\n        vm.startBroadcast();\n\n        // Approve Sablier to spend tokens\n        token.approve(lockupAddress, depositAmount);\n\n        // Build params\n        Lockup.CreateWithDurations memory params;\n        params.sender = msg.sender;\n        params.recipient = recipient;\n        params.depositAmount = depositAmount;\n        params.token = token;\n        params.cancelable = true;\n        params.transferable = true;\n\n        LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 });\n        LockupLinear.Durations memory durations = LockupLinear.Durations({\n            cliff: cliffDuration,\n            total: totalDuration\n        });\n\n        uint256 streamId = lockup.createWithDurationsLL(params, unlockAmounts, durations);\n\n        vm.stopBroadcast();\n    }\n}\n\n\nRun with:\n\nforge script script/CreateVestingStream.s.sol \\\n  --sig \"run(address,address,address,uint128,uint40,uint40)\" \\\n  <LOCKUP_ADDRESS> <TOKEN_ADDRESS> <RECIPIENT> <AMOUNT_WEI> <CLIFF_SECONDS> <TOTAL_SECONDS> \\\n  --rpc-url <RPC_URL> \\\n  --account <KEYSTORE_NAME> \\\n  --broadcast\n# Or: --ledger | --trezor | --private-key $ETH_PRIVATE_KEY\n\nImportant Notes\nToken decimals matter: Always convert human-readable amounts to wei (e.g., for 18-decimal tokens: amount * 1e18). Use cast --to-wei <amount> to convert.\nApprove first: The sender MUST approve the SablierLockup contract to spend the ERC-20 tokens before creating a stream.\nCancelable vs Non-cancelable: If cancelable is true, the sender can cancel and reclaim unvested tokens. Set to false for trustless vesting.\nTransferable: If true, the recipient can transfer the stream NFT to another address.\nGas costs: Linear streams are cheapest (~169k gas). Tranched streams cost more with more tranches (~300k for 4 tranches). Dynamic streams vary by segment count.\nStream NFT: Each stream is represented as an ERC-721 NFT owned by the recipient. The NFT can be transferred if the stream is transferable.\nMinimum Solidity version: v0.8.22 for the Lockup contracts.\nSablier UI: Streams can be viewed and managed at https://app.sablier.com\nQuick Reference: Duration Conversions\nDuration\tSeconds\n1 day\t86400\n1 week\t604800\n30 days\t2592000\n90 days (quarter)\t7776000\n180 days (half year)\t15552000\n365 days (1 year)\t31536000\n730 days (2 years)\t63072000\n1095 days (3 years)\t94608000\n1461 days (4 years)\t126230400\nResources\nDocs: https://docs.sablier.com\nLockup Source: https://github.com/sablier-labs/lockup\nExamples: https://github.com/sablier-labs/evm-examples/tree/main/lockup\nIntegration Template: https://github.com/sablier-labs/lockup-integration-template\nDeployment Addresses: https://docs.sablier.com/guides/lockup/deployments\nSablier App: https://app.sablier.com"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/sneg55/token-vesting",
    "publisherUrl": "https://clawhub.ai/sneg55/token-vesting",
    "owner": "sneg55",
    "version": "1.0.2",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/token-vesting",
    "downloadUrl": "https://openagent3.xyz/downloads/token-vesting",
    "agentUrl": "https://openagent3.xyz/skills/token-vesting/agent",
    "manifestUrl": "https://openagent3.xyz/skills/token-vesting/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/token-vesting/agent.md"
  }
}