{"id":19179400,"url":"https://github.com/rssblue/v4vts","last_synced_at":"2026-01-03T03:04:17.646Z","repository":{"id":254878939,"uuid":"847629436","full_name":"rssblue/v4vts","owner":"rssblue","description":"V4V calculations and transactions for Podcasting 2.0","archived":false,"fork":false,"pushed_at":"2024-08-31T18:59:36.000Z","size":48,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-11T02:54:40.393Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/v4vts","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rssblue.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-08-26T08:32:21.000Z","updated_at":"2024-12-27T23:40:46.000Z","dependencies_parsed_at":"2024-08-26T19:42:21.799Z","dependency_job_id":null,"html_url":"https://github.com/rssblue/v4vts","commit_stats":null,"previous_names":["rssblue/v4vts"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fv4vts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fv4vts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fv4vts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fv4vts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rssblue","download_url":"https://codeload.github.com/rssblue/v4vts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249845517,"owners_count":21333728,"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-09T10:43:00.881Z","updated_at":"2026-01-03T03:04:17.577Z","avatar_url":"https://github.com/rssblue.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# V4V (TypeScript)\n\nA set of helper functions for dealing with value-for-value (V4V) calculations and transactions.\nThe functionality is largely duplicated from the [v4v](https://github.com/rssblue/v4v) [Rust](https://www.rust-lang.org) crate.\n\n## All items\n\n### `v4v/calc`\n\n```ts\nfunction computeSatRecipients(splits: number[], totalSats: number): number[];\n```\n\n```ts\ntype ShareBasedRecipient = {\n  numShares: number;\n};\n\ntype PercentageBasedRecipient = {\n  percentage: number;\n};\n\ntype Recipient = ShareBasedRecipient | PercentageBasedRecipient;\n\nenum RecipientsToSplitsError\n\nfunction feeRecipientsToSplits(\n  recipients: Recipient[],\n): number[] | RecipientsToSplitsError\n```\n\n### `v4v/sockets`\n\n```ts\ntype LiveUpdateBlock\n\nfunction extractLiveUpdateBlock(data: any): LiveUpdateBlock | null\n```\n\n### `v4v/forwarding`\n\nTypes to use with [v4v (Rust)](https://docs.rs/v4v).\n\n```ts\ninterface PaymentInfo\n\ninterface KeysendAddress\n\ninterface PaymentRecipientInfo\n```\n\n## Examples\n\n### Sat forwarding service using `\u003cpodcast:liveItem\u003e` websocket\n\nThis assumes the usage of a signal-based framework like SolidJS.\n\n```ts\nimport QRCode from \"qrcode\";\nimport { PaymentInfo, PaymentRecipientInfo } from \"v4vts/forwarding\";\nimport { createMemo } from \"solid-js\";\nimport { extractLiveUpdateBlock } from \"v4vts/sockets\";\nimport { io } from \"socket.io-client\";\nimport { v4 as uuid } from \"uuid\";\n\n...\n\nconst socket = io(socketUrl);\nsocket.on(\"remoteValue\", function(data) {\n  const block = extractLiveUpdateBlock(data);\n  setRemoteBlock(block);\n  if (block !== null \u0026\u0026 block.value.destinations.length \u003e 0) {\n    setRemoteValueDestinations(block.value.destinations);\n  } else {\n    setRemoteValueDestinations(liveitemInfo.valueDestinations);\n  }\n});\n\n...\n\nconst recipients = createMemo(() =\u003e {\n  const genericRecipients: GenericRecipient[] = valueDestinations().map((dest) =\u003e {\n    if (dest.fee) {\n      return {\n        percentage: dest.split,\n      }\n    }\n    return {\n      numShares: dest.split,\n    }\n  });\n  const splits = feeRecipientsToSplits(genericRecipients);\n  if (!(splits instanceof Array)) {\n    return [];\n  }\n  const sats = computeSatRecipients(splits, numSats());\n\n  return valueDestinations().map((dest, i) =\u003e {\n    return {\n      name: dest.name,\n      address: {\n        pubkey: dest.address,\n        customData: (() =\u003e {\n          if (dest.customKey === null || dest.customValue === null) {\n            return null;\n          }\n          return [dest.customKey, dest.customValue];\n        })(),\n      },\n      numSats: sats[i],\n      paymentId: uuid(),\n\n    } as PaymentRecipientInfo;\n  });\n});\n\n...\n\ninterface ApiRequest {\n  paymentInfo: PaymentInfo;\n  recipients: PaymentRecipientInfo[];\n}\n\nconst onSubmit = async () =\u003e {\n  setState(\"fetching\");\n  const request: ApiRequest = {\n    paymentInfo: {\n      action: \"boost\",\n      feedGuid: liveitemInfo.feedGuid,\n      feedName: liveitemInfo.feedTitle,\n      feedPiId: null,\n      feedUrl: null,\n      itemGuid: liveitemInfo.itemGuid,\n      itemName: liveitemInfo.itemTitle,\n      itemPiId: null,\n      timestamp: null,\n      speed: null,\n      appName: \"lightning.phantompowermusic.io\",\n      appVersion: null,\n      senderName: author(),\n      senderId: null,\n      totalNumSats: numSats(),\n      message: message(),\n      boostLink: null,\n      boostId: uuid(),\n      remoteFeedGuid: remoteItemInfo()[0],\n      remoteItemGuid: remoteItemInfo()[1],\n      replyAddress: null,\n    },\n    recipients: recipients(),\n  };\n  const response = await generateInvoice(request);\n  const img = await QRCode.toDataURL(response.invoice, {\n    type: \"image/png\",\n    width: 2000,\n    // Dark mode:\n    color: {\n      dark: \"#ffffff\",\n      light: \"#080828\",\n    },\n    margin: 1,\n  });\n  setInvoice({\n    str: response.invoice,\n    img: img,\n  });\n  setState(\"success\");\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frssblue%2Fv4vts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frssblue%2Fv4vts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frssblue%2Fv4vts/lists"}