{"id":19881796,"url":"https://github.com/daniel0130/token-manager","last_synced_at":"2025-05-02T14:31:07.888Z","repository":{"id":259001405,"uuid":"876044508","full_name":"daniel0130/token-manager","owner":"daniel0130","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-22T05:51:06.000Z","size":5358,"stargazers_count":1,"open_issues_count":17,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-28T18:38:03.479Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daniel0130.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-21T10:00:10.000Z","updated_at":"2024-10-28T10:34:09.000Z","dependencies_parsed_at":"2024-10-22T12:59:31.909Z","dependency_job_id":null,"html_url":"https://github.com/daniel0130/token-manager","commit_stats":null,"previous_names":["daeniel/token-manager","daniel0130/token-manager"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel0130%2Ftoken-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel0130%2Ftoken-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel0130%2Ftoken-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel0130%2Ftoken-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daniel0130","download_url":"https://codeload.github.com/daniel0130/token-manager/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224315178,"owners_count":17290992,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T17:15:19.409Z","updated_at":"2024-11-12T17:15:20.054Z","avatar_url":"https://github.com/daniel0130.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Token Manager\n\n\u003cp align=\"center\"\u003e\n    An open protocol for issuing managed tokens on Solana.\n\u003c/p\u003e\n\n## Background\n\nThe Token Manager program is a wrapper protocol that achieves conditional ownership of Solana NFTs. It allows one to issue an NFT to another party with embedded mechanisms for programmatic management of the token while it sits in their wallet. Among others, things like time-based expiration, usage-based expiration, selective transferability, and non-transferability are possible with the Token Manager. Its modular design uses “plugin” invalidators, approval authorities, and transfer authorities modeled as separate smart contracts to allow for theoretically any custom invalidation, claiming, and transfer logic tied to on-chain data. We currently offer two out-of-the-box invalidator plugins to support basic time and usage-based expiration as well as a basic payment-based claim approver.\n\n## Addresses\n\nProgram addresses are the same on devnet, testnet, and mainnet-beta.\n\n- TokenManager: [`mgr99QFMYByTqGPWmNqunV7vBLmWWXdSrHUfV8Jf3JM`](https://explorer.solana.com/address/mgr99QFMYByTqGPWmNqunV7vBLmWWXdSrHUfV8Jf3JM)\n- PaidClaimApprover: [`pcaBwhJ1YHp7UDA7HASpQsRUmUNwzgYaLQto2kSj1fR`](https://explorer.solana.com/address/pcaBwhJ1YHp7UDA7HASpQsRUmUNwzgYaLQto2kSj1fR)\n- TimeInvalidator: [`tmeEDp1RgoDtZFtx6qod3HkbQmv9LMe36uqKVvsLTDE`](https://explorer.solana.com/address/tmeEDp1RgoDtZFtx6qod3HkbQmv9LMe36uqKVvsLTDE)\n- UseInvalidator: [`useZ65tbyvWpdYCLDJaegGK34Lnsi8S3jZdwx8122qp`](https://explorer.solana.com/address/useZ65tbyvWpdYCLDJaegGK34Lnsi8S3jZdwx8122qp)\n\n## Plugins\n\nToken-manager is made to be composable. It allows for plugins for\n\n1. Claim approvers\n2. Transfer authorities\n3. Invalidators\n4. Payment manager\n\nWhen instantiating a token-manager, the issuer can set a claim approver, transfer authority and invalidators that can control the claim, transfer and invalidate mechanisms. These are all plugins that can be pointed to any program-derived account or user owned account. Out of the box, there are basic plugins to power use and time rentals and subscriptions.\n\nAll of these are modeled are separate programs so users can choose to implement custom logic in a separate program for claim, transfer and invalidation. Payment manager handles payment splits for all payments associated with the programs and also has built in support for paying out creator royalties.\n\n# Documentation\n\n## Invalidation Types\n\nThe program generalizes the concept of invalidation into a list of invalidators so any of those public keys can trigger the invalidation of the token. The invalidation type field is used to specify \"what happens\" when invalidation is triggered.\n\n```\n#[derive(Clone, Debug, PartialEq, AnchorSerialize, AnchorDeserialize)]\n#[repr(u8)]\npub enum InvalidationType {\n    /// Upon invalidation it will be returned to the issuer\n    Return = 1,\n    /// Upon invalidation it will remain marked as invalid\n    Invalidate = 2,\n    /// Upon invalidation the token manager will be deleted and thus the tokens are released\n    Release = 3,\n    /// Upon invalidation the token manager will be reset back to issued state\n    Reissue = 4,\n}\n```\n\n## Claim Authority\n\nThe concept of claim_authority allows for the issuer to specify specific public key that can approve claiming of the tokens. This can be used in a few contexts.\n\n1. using Keypair.generate() to create a new private key and embed that into a URL as a one-time password. This allows only the recipient of this URL to claim the token. This provides an easy way to distribute tokens via off-chain systems like email.\n2. Paid claim approver, a program that is provided out of the box to enforce payment of a specified mint before claiming\n3. Checking ownership of a given NFT or token to only allow holders, members in DAOs etc to claim the token.\n4. Many, many more!\n\nIf not set, this token can be claimed by anyone.\n\n## Transfer Authority\n\nSimilar to claim_authority, but a specified publickey that can approve transfer of this token. Because the tokens by default are frozen, normal transfer does not work. Instead a transfer_authority can be used to allow transfers to specified wallet. By default, transfer_authority is set to the token_manager itself, rendering it non-transferable. If unset, the token can be freely transferred. The transfer is modeled by receiving a transfer_receipt that can be used to claim the token. Use cases of transfer_authority can include\n\n1. A system that groups wallets together allowing transfer between \"known\" wallets all owned by the same user\n2. Encoding a transfer schedule in a program on-chain to approve a rotation of the token, similar to a time-share.\n3. A paid transfer that allows someone to pay some amount to the current holder in order to get approved for transfer\n4. KYC approval required before transfer\n5. Transfer between members of a DAO / multi-sig\n6. ...\n\nNOTE: Once approved for transfer, the approved party can claim the token from the current holder. This is due to the fact that as a recipient (new holder) you must sign the transaction to \"accept\" the tokens, because part of this process involved delegating the tokens back to the token-manager. This means that approved parties can effectively \"take\" the token from the current holder.\n\n## Receipts\n\nThe concept of receipts allows the issuer of token(s) into a token-manager to mint a receipt NFT representing this token-manager. Coupled with InvalidationType::Return above, the receipt can be freely traded and represent the public key that the token(s) will be returned to when they are invalidated. This essentially represents the underlying asset during outstanding rentals.\n\n- Receipts are dynamically minted using the image-generator in https://github.com/solana-nft-programs/generator. This allows it to be completely on-chain NFT\n- Receipts are freely tradeable and represent the underlying asset for outstanding rentals.\n- Receipts will become expired after the rental is over, This means the user must manual follow the links in the description to burn the expired receipt.\n\n## Token Manager ERD\n\n\u003cimg width=\"877\" alt=\"DIAGRAM\" src=\"https://user-images.githubusercontent.com/7113086/157140752-02983b0d-3501-42dd-add6-ea29fa37be80.png\"\u003e\nView Online: https://dbdiagram.io/d/6226977961d06e6eadbc77be\n\nDocumentation is a work in progress. For now, one should read [the tests](https://github.com/solana-nft-programs/token-manager/blob/main/tests/issueUnissue.spec.ts).\n\nWe soon plan on releasing a React library to make it easy to integrate ui components with your frontend.\n\n## Example usage\n\n#### All token issue parameters\n\n```js\nexport type IssueParameters = {\n  // Mint of the tokens this token manager will manager\n  mint: PublicKey,\n\n  // Amoun of tokens to put into token manager, for NFTs this should use the default of 1\n  amount?: BN,\n\n  // Token account where the token is currently held\n  issuerTokenAccountId: PublicKey,\n\n  // Whether anyone can claim this or only the specified person with the link\n  visibility?: \"private\" | \"public\",\n\n  // What kind of token manager this is\n  // /// Token a managed rental and will use freeze authority to manage the token\n  // Managed = 1,\n  // /// Token is unmanaged and can be traded freely until expiration\n  // Unmanaged = 2,\n  // /// Token is a metaplex edition and so it uses metaplex program to freeze\n  // Edition = 3,\n  kind?: TokenManagerKind,\n\n  // Optional parameters to specify an up front payment that must be paid before claim\n  claimPayment?: {\n    // Mint of the tokens required for payment\n    paymentMint: PublicKey,\n    // Amount of the tokens required for payment\n    paymentAmount: number,\n  },\n\n  // Optional parameters to expire this token manager based on time\n  timeInvalidation?: {\n    // Optional payment manager to handle payment splits if multiple parties involved - should be a PDA initialized by payment manager program\n    paymentManager?: PublicKey,\n    // Optional duration after token is claimed in seconds\n    durationSeconds?: number,\n    // Optional exact fixed expiration in UTC seconds, not to be used along with durationSeconds\n    maxExpiration?: number,\n    // Optional extension parameters to extend duration\n    extension?: {\n      // The amount rate needed for extension\n      extensionPaymentAmount: number,\n      // The duration added based on amount paid\n      extensionDurationSeconds: number,\n      // The mint to accept payment for extension\n      paymentMint: PublicKey,\n      // Whether this rental allows for partial extension or only in increments of extensionDurationSeconds\n      disablePartialExtension?: boolean,\n    },\n  },\n\n  // Optional parameters to expire this token manager based on usage\n  useInvalidation?: {\n    // Optional payment manager to handle payment splits if multiple parties involved - should be a PDA initialized by payment manager program\n    paymentManager?: PublicKey,\n    // Optional total usages allocated\n    totalUsages?: number,\n    // Optional use authority who can use this token\n    useAuthority?: PublicKey,\n    // Optional extension parameters to extend usages\n    extension?: {\n      // Number of usages to extend for this payment amount\n      extensionUsages: number,\n      // The mint to accept payment for extension\n      extensionPaymentMint: PublicKey,\n      // The amount needed to extend usages\n      extensionPaymentAmount: number,\n      // Optional limit for how many usages can be extended\n      maxUsages?: number,\n    },\n  },\n\n  // What happens to the token upon invalidation\n  // /// Upon invalidation it will be returned to the issuer\n  // Return = 1,\n  // /// Upon invalidation it will remain marked as invalid\n  // Invalidate = 2,\n  // /// Upon invalidation the token manager will be deleted and thus the tokens are released\n  // Release = 3,\n  invalidationType?: InvalidationType,\n\n  // Whether the issuer wants to claim a receipt NFT from their rental - this receipt allows the issuer to trade the underlying asset and future rental income\n  receipt?: boolean,\n};\n```\n\n---\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n### Javascript create fixed price 24h rental\n\n```\nnpm i @solana-nft-programs/token-manager\n```\n\n```javascript\nimport { Connection } from \"@solana/web3.js\";\n\n// payment amount 10 for duration of 86400 seconds (24 hours)\nconst issueTokenParameters = {\n  paymentAmount: new BN(10),\n  paymentMint: new PublicKey(\"...\"),\n  durationSeconds: 86400,\n  mint: new PublicKey(\"...\"), // NFT rental mint\n  issuerTokenAccountId: new PublicKey(\"...\"),\n  visibility: \"public\", // default public means anyone can claim this rental\n  kind: TokenManagerKind.Edition, // used for metaplex master / editions,\n  invalidationType: InvalidationType.Return, // indicates this token will be returned when invalidated\n};\n\ntry {\n  const [transaction] = await issueToken(issueTokenParameters);\n  transaction.feePayer = wallet.publicKey;\n  transaction.recentBlockhash = (\n    await connection.getRecentBlockhash(\"max\")\n  ).blockhash;\n  transaction.sign(wallet, masterEditionMint);\n  await sendAndConfirmRawTransaction(connection, transaction.serialize(), {\n    commitment: \"confirmed\",\n  });\n} catch (exception) {\n  // handle exception\n}\n```\n\n---\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n### Javascript create single use ticket example\n\n```\nnpm i @solana-nft-programs/token-manager\n```\n\n```javascript\nimport { Connection } from \"@solana/web3.js\";\n\n// no payment specified, 1 usage and private link means only the holder of the link can claim it\n// Releases on use as a memento\nconst issueTokenParameters = {\n  useInvalidation: { totalUsages: 1 }, // 1 use\n  mint: new PublicKey(\"...\"), // ticket mint\n  issuerTokenAccountId: new PublicKey(\"...\"),\n  visibility: \"private\", // private so you can send this out via email\n  kind: TokenManagerKind.Edition, // used for metaplex master / editions,\n  invalidationType: InvalidationType.Release, // indicates this token will be released after being used\n};\n\ntry {\n  const [transaction] = await issueToken(issueTokenParameters);\n  transaction.feePayer = wallet.publicKey;\n  transaction.recentBlockhash = (\n    await connection.getRecentBlockhash(\"max\")\n  ).blockhash;\n  transaction.sign(wallet, masterEditionMint);\n  await sendAndConfirmRawTransaction(connection, transaction.serialize(), {\n    commitment: \"confirmed\",\n  });\n} catch (exception) {\n  // handle exception\n}\n```\n\n---\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n---\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniel0130%2Ftoken-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaniel0130%2Ftoken-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniel0130%2Ftoken-manager/lists"}