{"id":15644993,"url":"https://github.com/paulmillr/micro-sol-signer","last_synced_at":"2025-04-05T00:04:59.852Z","repository":{"id":44418294,"uuid":"512497098","full_name":"paulmillr/micro-sol-signer","owner":"paulmillr","description":"Create, sign \u0026 decode Solana transactions with minimum deps","archived":false,"fork":false,"pushed_at":"2025-03-27T22:26:35.000Z","size":728,"stargazers_count":75,"open_issues_count":3,"forks_count":10,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-28T23:03:21.627Z","etag":null,"topics":["account","address","decoder","signer","sol","solana","transaction","tx"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/paulmillr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/funding.yml","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},"funding":{"github":"paulmillr"}},"created_at":"2022-07-10T17:28:49.000Z","updated_at":"2025-03-28T12:06:09.000Z","dependencies_parsed_at":"2024-10-03T12:06:30.671Z","dependency_job_id":"7048ca2e-9cc7-4463-9b43-f845ad300e9c","html_url":"https://github.com/paulmillr/micro-sol-signer","commit_stats":{"total_commits":19,"total_committers":1,"mean_commits":19.0,"dds":0.0,"last_synced_commit":"05b6ce0538a1ea4a38ea146b9bc4cb01dea84fbd"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulmillr%2Fmicro-sol-signer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulmillr%2Fmicro-sol-signer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulmillr%2Fmicro-sol-signer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulmillr%2Fmicro-sol-signer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paulmillr","download_url":"https://codeload.github.com/paulmillr/micro-sol-signer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266563,"owners_count":20910836,"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":["account","address","decoder","signer","sol","solana","transaction","tx"],"created_at":"2024-10-03T12:03:55.113Z","updated_at":"2025-04-05T00:04:59.829Z","avatar_url":"https://github.com/paulmillr.png","language":"JavaScript","funding_links":["https://github.com/sponsors/paulmillr"],"categories":[],"sub_categories":[],"readme":"# micro-sol-signer\n\nCreate, sign \u0026 decode Solana transactions with minimum deps.\n\n- 🔓 Secure: minimum deps, audited [noble](https://paulmillr.com/noble/) cryptography\n- 🔻 Tree-shakeable: unused code is excluded from your builds\n- ✍️ Create, sign and decode transactions\n- 🎶 Supports [Codama IDL](https://github.com/codama-idl/codama) and Token2022\n- 🪶 Minimal\n\n_Check out all web3 utility libraries:_ [ETH](https://github.com/paulmillr/micro-eth-signer), [BTC](https://github.com/paulmillr/scure-btc-signer), [SOL](https://github.com/paulmillr/micro-sol-signer), [tx-tor-broadcaster](https://github.com/paulmillr/tx-tor-broadcaster)\n\n## Usage\n\n\u003e `npm install micro-sol-signer`\n\n\u003e `jsr add jsr:@paulmillr/micro-sol-signer`\n\n```ts\nimport * as sol from 'micro-sol-signer';\n```\n\nMethod summary:\n\n```ts\nfunction formatPrivate(privateKey: Bytes, format: 'base58' | 'hex' | 'array' = 'base58');\nfunction getPublicKey(privateKey: Bytes);\nfunction getAddress(privateKey: Bytes);\nfunction getAddressFromPublicKey(publicKey: Bytes);\nfunction getPublicKeyFromAddress(address: string)\nfunction signTx(privateKey: Bytes, data: TxData): Promise\u003c[string, string]\u003e;\nfunction verifyTx(tx: TxData);\nfunction createTx(from: string, to: string, amount: bigint | string, blockhash: string);\nfunction createTxComplex(address: string, instructions: Instruction[], blockhash: string);\nfunction defineProgram\u003cT extends Record\u003cstring, MethodHint\u003cany\u003e\u003e\u003e;\nfunction isOnCurve(bytes: Bytes | string);\nfunction parseInstruction(instr: Instruction, tl: TokenList): any;\nfunction programAddress(program: string, ...seeds: Bytes[]);\nfunction tokenAddress(mint: string, owner: string, allowOffCurveOwner = false);\nfunction tokenFromSymbol(symbol: string, tokens = COMMON_TOKENS);\nfunction validateAddress(address: string);\n```\n\nThere are other variables such as `SYS_PROGRAM`, which are also exported. Specific features:\n\n- [Create and sign simple transaction](#create-and-sign-simple-transaction)\n- [Decode transaction](#decode-transaction)\n- [Create complex transactions and send tokens](#create-complex-transactions-and-send-tokens)\n- [Network](#network)\n- [ABI / API](#abiapi)\n\n### Create and sign simple transaction\n\n```js\n// 11111111... private key\nconst privKey = new Uint8Array(32).fill(0x01);\n// Get address of private key\nconst address = sol.getAddress(privKey);\n// AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9\n\n// Format private key\nconst privFormatted = sol.formatPrivate(privKey);\n// 2AXDGYSE4f2sz7tvMMzyHvUfcoJmxudvdhBcmiUSo6iuCXagjUCKEQF21awZnUGxmwD4m9vGXuC3qieHXJQHAcT\n\n// Simple tx (transfer some sol's from one account to another)\nconst toAddress = 'FDwkzWGxx6LfCfzcmVVLEk3QUMxNhuFuKEMRwzR4Dtys';\nconst blockhash = 'J2BjKU6L83eehHVgoze6uTXGCBu6nbxsqEro9QvWpU52';\nconst amount = '10.1';\nconst fee = '1.2';\nconst tx0 = sol.createTx(address, toAddress, amount, fee, blockhash);\n// AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDiojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1zTVICVf7+to6zQ/+XautpF+KSSoZ7ESTxv3rg8xPqyXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ORj/WtXHGLCh9wC0eGkf26qTFR5x3nCqwXXmoVtZb0BAgIAAQwCAAAAAMUBWgIAAAA=\nconst [txHash0, signedTx0] = sol.signTx(privKey, tx0);\n/*\n{\n  txHash0: '3wL4PdgBr3J2r4uuUrf4MU7HhgrJg6re2YRBeAwsZpYZRHgSgUAJymLRu6GcnKk7ZuR3F5UgPRTNj1mbzv966PTy',\n  signedTx0: 'AZLiibj05SPRhNU/o3ntK/7+aNQ8H/3HGLdh6zjxZKdyrKJEhjUjWDlMnEF2x0U/8JsKYsMMiYvQShkuNfpYdAwBAAEDiojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1zTVICVf7+to6zQ/+XautpF+KSSoZ7ESTxv3rg8xPqyXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ORj/WtXHGLCh9wC0eGkf26qTFR5x3nCqwXXmoVtZb0BAgIAAQwCAAAAAMUBWgIAAAA='\n}\n  */\n```\n\n### Decode transaction\n\n```js\nconst { base64 } = require('@scure/base');\nconst sol = require('../lib');\n\n// Random USDT tx from explorer:\n// https://explorer.solana.com/tx/5Nnhjv1GVB8T1k8MguUGHQw5zQQQsWET1f1zzj8azRhnVoYQPoZPtkscPCKy6FisP2eVWehjU1EYV8zywqKm5if4\nconst tx =\n  'Atrba9P4rJ4tA3fMXioF+LBR5Y397TCaCC7o/JsViIFxDQ+FOpW2/I+DGMtapWPmrRJ3KDEaYa21YbpUcXaygQPKXDfudpRNZKsMsjhhH018U2YKTAJoqu6Jr1jASfnV98/65boYyPzPujo4YMKnIaCjrt1EsvnPNCuoBMXUEzYAAgEECc20MANIMI92j1eVfOiH5WQ691HznE9ZeQfjeXpDNm0eH5z5eohWokD+6H+jjnZ/KFqkCmlEdPrk6HCx+mOgjTAJUM/3r5vR1DjJnZhT6PQK3Z32pIe8MzDmPxe8Ttzy2CTxiTfFaNQeAkRJCefcB5JJGeb/Qxrj4dpxv8Kv9gClJ544V5wdVgmhBbCFO1kSIv6OaEUizyYdqhTUiO8w8XsGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAM4BDmCv7bInF71jGS9UFFo/llozu4LSxwKess4eIIJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqYUpqsC9KfFD7lsris1C7YZkNRdSH5qix9nMo2igoP0yAgcDAgUBBAQAAAAIBAMGBAAKDJSDxgMPAAAABg==';\nconst decodedTx = sol.Transaction.decode(base64.decode(tx));\n\nconsole.log(decodedTx);\n/*\n{\n  msg: {\n    feePayer: 'EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB',\n    blockhash: '9xp5Jz2v7ZsE3Xn5SVGkRisjo7h16vzF1ducwhWnc5n9',\n    instructions: [ [Object], [Object] ]\n  },\n  signatures: {\n    EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB: Uint8Array(64),\n    '38QU8LKVK1Ew5uzsqttamNTTFxvnfzgi2ACQvj3ekuom': Uint8Array(64),\n  }\n}\n*/\n\nconsole.log(\n  decodedTx.msg.instructions.map((i) =\u003e {\n    return sol.parseInstruction(i, {\n      ...sol.COMMON_TOKENS,\n      // You can add custom tokens here if needed\n    });\n  })\n);\n/*\n[\n  {\n    type: 'advanceNonce',\n    info: {\n      nonceAccount: 'dN98UQCp6Hq9kedJKEczHt5B53tsC8eENv9cGEVwvuD',\n      nonceAuthority: '38QU8LKVK1Ew5uzsqttamNTTFxvnfzgi2ACQvj3ekuom'\n    },\n    hint: 'Consume nonce in nonce account=dN98UQCp6Hq9kedJKEczHt5B53tsC8eENv9cGEVwvuD (owner: 38QU8LKVK1Ew5uzsqttamNTTFxvnfzgi2ACQvj3ekuom)'\n  },\n  {\n    type: 'transferChecked',\n    info: {\n      amount: 64487850900n,\n      decimals: 6,\n      source: '3VDHywae15vgbG2euNPpwoHTEr2eyGLuS6EoF74kDkp4',\n      mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',\n      destination: '3feqC1fmo5YHMh2iw7X9kGE9F8P147hiiDQqC5xtSbpN',\n      owner: 'EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB'\n    },\n    hint: 'Transfer 64487.8509 USDT from token account=3VDHywae15vgbG2euNPpwoHTEr2eyGLuS6EoF74kDkp4 of owner=EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB to 3feqC1fmo5YHMh2iw7X9kGE9F8P147hiiDQqC5xtSbpN'\n  }\n]\n*/\n```\n\n### Create complex transactions and send tokens\n\nSolana is very flexible and has awesome architecture, but it also means there is no\n'right' way to send tokens:\n\n- Basic account (ex. EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB) cannot have any tokens\n- For every token contract you need to create separate token account which will be controlled by token contract\n- If a user gives some address to send tokens, it can be:\n  - Solana account: which means we need to derive token account address (sol.tokenAddress)\n  - Token account: it is possible the token account has not yet been created,\n    in this case we can create it for user; but it is not free, and will cost some fee.\n  - token account which was derived in a different way from main solana account\n\nThe basic token sending example is:\n\n```js\n// Current blockhash\nconst blockhash = '9xp5Jz2v7ZsE3Xn5SVGkRisjo7h16vzF1ducwhWnc5n9';\n// Sol account which is owner of tokenAccount\nconst fromAccount = 'EqywLUZcm73PSWri93X3M5TN62iFMsUPMjvWYUq89dKB';\n\nconst USDT = sol.tokenFromSymbol('USDT', {\n  ...sol.COMMON_TOKENS,\n  // You can add custom tokens here\n});\n// Deriving token account address from solana account address\nconst fromTokenAccount = sol.tokenAddress(USDT.contract, fromAccount);\n\n// Should be valid token account, not solana account\nconst toTokenAddress = 'FDwkzWGxx6LfCfzcmVVLEk3QUMxNhuFuKEMRwzR4Dtys';\n\nconst amount = '64487.8509';\nconst tokenSimple = sol.createTxComplex(\n  fromAccount, // owner of source token account (solana account)\n  [\n    sol.token.transferChecked({\n      source: fromTokenAccount, // Source token account (not solana account)\n      amount: sol.parseDecimal(amount, USDT.decimals),\n      decimals: USDT.decimals, // decimals of value\n      mint: USDT.contract, // token contract address\n      owner: fromAccount, // owner of source token account (solana account)\n      destination: toTokenAddress,\n    }),\n  ],\n  blockhash\n);\nconsole.log(tokenSimple);\n/*\n  AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIFzbQwA0gwj3aPV5V86IflZDr3UfOcT1l5B+N5ekM2bR4k8Yk3xWjUHgJESQnn3AeSSRnm/0Ma4+Hacb/Cr/YApdNUgJV/v62jrND/5dq62kX4pJKhnsRJPG/euDzE+rJezgEOYK/tsicXvWMZL1QUWj+WWjO7gtLHAp6yzh4ggmQG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqYUpqsC9KfFD7lsris1C7YZkNRdSH5qix9nMo2igoP0yAQQEAQMCAAoMlIPGAw8AAAAG\n  */\n```\n\nHowever, in real world you may need more complex logic, like:\n\n```js\n// Check if account is valid token account\nfunction isValidTokenAccount(mint, info, owner) {\n  if (!info) return false;\n  if (info.owner !== sol.TOKEN_PROGRAM) return false;\n  try {\n    const data = sol.TokenAccount.decode(info.data);\n    if (data.mint !== mint) return false;\n    if (data.state !== 'initialized') return false;\n    if (owner \u0026\u0026 data.owner !== owner) return false;\n    return true;\n  } catch (e) {\n    return false;\n  }\n}\n\nasync function solAccountInfo(address) {\n  // Network code, outside of scope of this package\n\n  // Call rpc with getAccountInfo\n  const res = await rpcCall('getAccountInfo', address, {\n    encoding: 'base64',\n    commitment: 'confirmed',\n  });\n  if (res.value === null) return undefined;\n  const [data, encoding] = res.value.data;\n  if (encoding !== 'base64') throw new Error(`SOL: invalid data encoding=${encoding}`);\n  return {\n    lamports: BigInt(res.value.lamports),\n    owner: res.value.owner,\n    rentEpoch: res.value.rentEpoch,\n    data: base64.decode(data),\n    exec: !!res.value.executable,\n  };\n}\n\nasync function createTx(\n  fromAddress, // our address (solana account), from which we will send tokens\n  toAddress, // user provided address to send tokens in.\n  tokenContract, // token contract address\n  decimals, // decimals of contract\n  amount, // string with amount of tokens to send\n  blockhash // current block hash\n) {\n  // Derive token account from 'from' address\n  const fromTokenAccount = sol.tokenAddress(tokenContract, fromAccount);\n  // Common token options\n  const tokenOpt = {\n    source: fromTokenAccount,\n    amount: sol.parseDecimal(amount, decimals),\n    decimals,\n    mint: tokenContract,\n    owner: fromAddress, // owner of source\n  };\n  // If address is on curve, it is probably not 'associated token contract'\n  if (sol.isOnCurve(toAddress)) {\n    // Derive token account from solana account\n    const toTokenAddress = sol.tokenAddress(tokenContract, toAddress);\n    const [addrInfo, assocInfo] = await Promise.all([\n      solAccountInfo(toAddress),\n      solAccountInfo(toTokenAddress),\n    ]);\n    // toTokenAddress -- is valid token account, we can send here\n    if (isValidTokenAccount(tokenContract, assocInfo, toAddress)) {\n      // Associted account is ok, send to toTokenAddress\n      return sol.createTxComplex(\n        fromAddress,\n        [sol.token.transferChecked({ ...tokenOpt, destination: toTokenAddress })],\n        blockhash\n      );\n      // toTokenAddress is not valid token account, but toAddress is (even if it is on-curve)\n    } else if (isValidTokenAccount(tokenContract, addrInfo)) {\n      // account is actually token account, send to address. But since we don't know\n      return sol.createTxComplex(\n        fromAddress,\n        [sol.token.transferChecked({ ...tokenOpt, destination: toAddress })],\n        blockhash\n      );\n      // There is no valid token accounts, but toAddress is basic solana account and we can create\n      // token account for it\n    } else if (addrInfo \u0026\u0026 addrInfo.owner === sol.SYS_PROGRAM) {\n      // try to create assoc address and send tokens to it\n      return sol.createTxComplex(\n        fromAddress,\n        [\n          sol.associatedToken.create({\n            source: fromAddress,\n            account: toTokenAddress,\n            wallet: toAddress,\n            mint: tokenContract,\n          }),\n          sol.token.transferChecked({ ...tokenOpt, destination: toTokenAddress }),\n        ],\n        blockhash\n      );\n    } else {\n      // We probably can create associated account here, even if account doesn't exists, but it is probably typo and funds will be lost\n      throw new Error(\n        `SOL.createTx: invalid token destination address, account=${toAddress} doesn't exists, associated=${assocAddress} doesn't exists`\n      );\n    }\n  } else {\n    // Address is off-curve: which means it should be associated token account\n    const info = await solAccountInfo(toAddress);\n    // We cannot create associated address here, since given address is already off-curve\n    if (!isValidTokenAccount(tokenContract, info))\n      throw new Error(\n        `SOL.createTx: invalid token destination address=${toAddress}, off-curve and invalid`\n      );\n    // Valid token addr, send to it. Send to address\n    return sol.createTxComplex(\n      fromAddress,\n      [sol.token.transferChecked({ ...tokenOpt, destination: toAddress })],\n      blockhash\n    );\n  }\n  throw new Error('SOL.createTx: unexpected case');\n}\n```\n\n### Network\n\n```ts\nimport * as net from 'micro-sol-signer/net.js';\n// Docs available in src/net.ts\n```\n\nThe section should be written; for now check out source code for network-related methods, like fetching balances, or sending txs.\n\nNo real network calls are done in the library for simplified auditing. When network needs to be used, JSON-RPC compatible provider is required. We have one in [micro-ftch](https://github.com/paulmillr/micro-ftch) and it's recommended for usage.\n\n### ABI/API\n\nThere is no official ABI for Solana (in comparison with ethereum), but it is actually not a big deal,\nsince you will need to write API on top of raw ABI anyway.\nWe have small DSL, based on [micro-packed](https://github.com/paulmillr/micro-packed),\nwhich allows to define ABI easier: look at `sol.token` definition in `index.ts`.\n\n## License\n\nMIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulmillr%2Fmicro-sol-signer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaulmillr%2Fmicro-sol-signer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulmillr%2Fmicro-sol-signer/lists"}