diff --git a/examples/solana_anchor/expenses_splitting/.vscode/settings.json b/examples/solana_anchor/expenses_splitting/.vscode/settings.json new file mode 100644 index 0000000..b448937 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + "./program/programs/ExpenseSplitter/Cargo.toml" + ] +} diff --git a/examples/solana_anchor/expenses_splitting/README.md b/examples/solana_anchor/expenses_splitting/README.md new file mode 100644 index 0000000..feebb17 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/README.md @@ -0,0 +1,43 @@ +# Expenses splitting + +This program does expenses splitting on Solana. The program manages group accounts. +Anyone can create a group by providing addresses and initial balances. The group account holds balances for each of its members. +The sum of balances of a group always sum zero. +Members can log payments by providing addresses, payments and participation factors. +Group members can add and remove members. + +# Program template + +Template that includes the CIDL, and the generated code with its business logic. + +Creates a user, and tracks the income and outcome amount per user + +## Generate code + +```shell +codigo solana generate cidl.yaml -a +``` + +## Build contract + +From within the program directory + +```shell +anchor build +``` + +## Install program client dependencies + +From within the program_client directory + +```shell +npm install +``` + +## Test contract + +From within the program directory + +```shell +anchor test +``` diff --git a/examples/solana_anchor/expenses_splitting/cidl.yaml b/examples/solana_anchor/expenses_splitting/cidl.yaml new file mode 100644 index 0000000..9e15978 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/cidl.yaml @@ -0,0 +1,112 @@ +cidl: "0.9" +info: + name: ExpenseSplitter + title: Expense Splitter Contract + version: "1.0.0" + summary: A contract for managing group expenses, allowing members to split costs fairly. + contact: + name: "Developer Name" + web: "https://developerwebsite.com" + email: "contact@developerwebsite.com" + git: "https://github.com/developer/ExpenseSplitter" + license: + name: "MIT License" + identifier: "MIT" + url: "https://opensource.org/licenses/MIT" + +solana: + seeds: + groupAccount: + items: + - name: "group_name" + type: "string" + +types: + GroupAccount: + summary: Represents a group account with members and their balances. + fields: + - name: "group_name" + type: "string" + description: "The name of the group." + attributes: [cap=100] + - name: "members" + type: "array" + description: "Array of member public keys." + attributes: [cap=100] + - name: "balances" + type: "array" + description: "Array of member balances in lamports." + attributes: [cap=100] + +methods: + - name: "createGroup" + summary: "Creates a new group with initial members and balances." + inputs: + - name: "group_account" + type: sol:account + attributes: [ sol:init ] + - name: "group_name" + type: "string" + description: "The name of the group." + - name: "members" + type: "array" + description: "Array of member public keys." + attributes: [cap=100] + - name: "initial_balances" + type: "array" + description: "Array of initial balances for each member." + attributes: [cap=100] + solana: + default-payer: true + + - name: "addMember" + summary: "Adds a new member to the group." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "new_member" + type: "sol:pubkey" + description: "Public key of the new member." + + - name: "removeMember" + summary: "Removes a member from the group." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "member" + type: "sol:pubkey" + description: "Public key of the member to remove." + + - name: "logPayment" + summary: "Logs a payment made by or for group members, updating balances accordingly." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "title" + type: "string" + description: "Title or description of the payment." + - name: "participants" + type: "array" + description: "Array of participating member public keys." + attributes: [cap=100] + - name: "amounts" + type: "array" + description: "Array of amounts each participant gave to the group." + attributes: [cap=100] + - name: "participation_factors" + type: "array" + description: "Array of participation factors for each participant." + attributes: [cap=100] + +errors: + - id: "GroupBalanceError" + msg: "The sum of balances in the group must always sum to zero." + - id: "InvalidMemberError" + msg: "The specified member does not exist in the group." + - id: "InvalidGroupError" + msg: "The specified group does not exist." + - id: "BalanceMismatchError" + msg: "The sum of initial balances does not sum to zero." \ No newline at end of file diff --git a/examples/solana_anchor/expenses_splitting/program/.gitignore b/examples/solana_anchor/expenses_splitting/program/.gitignore new file mode 100644 index 0000000..59a9188 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/.gitignore @@ -0,0 +1,10 @@ +# This file is auto-generated from the CIDL source. +# Editing this file directly is not recommended as it may be overwritten. + +.anchor +.DS_Store +target +**/*.rs.bk +node_modules +test-ledger +.yarn diff --git a/examples/solana_anchor/expenses_splitting/program/.prettierignore b/examples/solana_anchor/expenses_splitting/program/.prettierignore new file mode 100644 index 0000000..9419392 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/.prettierignore @@ -0,0 +1,10 @@ +# This file is auto-generated from the CIDL source. +# Editing this file directly is not recommended as it may be overwritten. + +.anchor +.DS_Store +target +node_modules +dist +build +test-ledger diff --git a/examples/solana_anchor/expenses_splitting/program/Anchor.toml b/examples/solana_anchor/expenses_splitting/program/Anchor.toml new file mode 100644 index 0000000..8f0dcf3 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/Anchor.toml @@ -0,0 +1,21 @@ +# This file is auto-generated from the CIDL source. +# Editing this file directly is not recommended as it may be overwritten. + +[toolchain] + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +workspace = "CBdZHhteKeEySFipeE9w2zRsigs3CsGyFEQ9UKwEpfJ5" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/examples/solana_anchor/expenses_splitting/program/Cargo.lock b/examples/solana_anchor/expenses_splitting/program/Cargo.lock new file mode 100644 index 0000000..5c5d481 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/Cargo.lock @@ -0,0 +1,1730 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ExpenseSplitter" +version = "1.0.0" +dependencies = [ + "ahash 0.8.6", + "anchor-lang", + "bumpalo", + "solana-program", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.12", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom 0.2.12", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-program", + "anchor-derive-accounts", + "anchor-derive-serde", + "anchor-derive-space", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.3", + "bytemuck", + "getrandom 0.2.12", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.6", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b439809cdfc0d86ecc7317f1724df13dfa665df48991b79e90e689411451f7" +dependencies = [ + "ark-bn254", + "ark-ff", + "thiserror", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "solana-frozen-abi" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdc9268b1abba206e1a8a234473eb5f7f7af660a86e4d468e7e79b3e5667aa9" +dependencies = [ + "ahash 0.8.6", + "blake3", + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "byteorder", + "cc", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86118cc8437c60d1a474501f6095df880aaac422ab04523a984015c5b7334428" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.52", +] + +[[package]] +name = "solana-program" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c940ad70659c2366331fd136d67cc968acadbc1f6ad13cff9ffe7e392aa831" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags 2.4.2", + "blake3", + "borsh 0.10.3", + "borsh 0.9.3", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.12", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint", + "num-derive", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1da4a7421c09ee4dbf81df06407933d4f68f8990ae87a2feaee6e1b03c97d1d" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.52", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] diff --git a/examples/solana_anchor/expenses_splitting/program/Cargo.toml b/examples/solana_anchor/expenses_splitting/program/Cargo.toml new file mode 100644 index 0000000..c4769fb --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/Cargo.toml @@ -0,0 +1,16 @@ +# This file is auto-generated from the CIDL source. +# Editing this file directly is not recommended as it may be overwritten. + +[workspace] +members = [ + "programs/*" +] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/examples/solana_anchor/expenses_splitting/program/app/program_client/index.ts b/examples/solana_anchor/expenses_splitting/program/app/program_client/index.ts new file mode 100644 index 0000000..0220ab9 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/app/program_client/index.ts @@ -0,0 +1,5 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +export * from "./pda" +export * from "./rpc" \ No newline at end of file diff --git a/examples/solana_anchor/expenses_splitting/program/app/program_client/pda.ts b/examples/solana_anchor/expenses_splitting/program/app/program_client/pda.ts new file mode 100644 index 0000000..267976b --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/app/program_client/pda.ts @@ -0,0 +1,22 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +import {PublicKey} from "@solana/web3.js"; +import {BN} from "@coral-xyz/anchor"; + +export type GroupAccountSeeds = { + groupName: string, +}; + +export const deriveGroupAccountPDA = ( + seeds: GroupAccountSeeds, + programId: PublicKey +): [PublicKey, number] => { + return PublicKey.findProgramAddressSync( + [ + Buffer.from(seeds.groupName, "utf8"), + ], + programId, + ) +}; + diff --git a/examples/solana_anchor/expenses_splitting/program/app/program_client/rpc.ts b/examples/solana_anchor/expenses_splitting/program/app/program_client/rpc.ts new file mode 100644 index 0000000..9f159b3 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/app/program_client/rpc.ts @@ -0,0 +1,424 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +import { + AnchorProvider, + BN, + IdlAccounts, + Program, + web3, +} from "@coral-xyz/anchor"; +import { MethodsBuilder } from "@coral-xyz/anchor/dist/cjs/program/namespace/methods"; +import { ExpenseSplitter } from "../../target/types/expense_splitter"; +import idl from "../../target/idl/expense_splitter.json"; +import * as pda from "./pda"; + + + +let _program: Program; + + +export const initializeClient = ( + programId: web3.PublicKey, + anchorProvider = AnchorProvider.env(), +) => { + _program = new Program( + idl as never, + programId, + anchorProvider, + ); + + +}; + +export type CreateGroupArgs = { + feePayer: web3.PublicKey; + groupName: string; + members: web3.PublicKey[]; + initialBalances: bigint[]; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link MethodsBuilder} + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const createGroupBuilder = ( + args: CreateGroupArgs, + remainingAccounts: Array = [], +): MethodsBuilder => { + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _program.programId); + + return _program + .methods + .createGroup( + args.groupName, + args.members, + args.initialBalances.map(e => new BN(e.toString())), + args.groupAccountSeedGroupName, + ) + .accounts({ + feePayer: args.feePayer, + groupAccount: groupAccountPubkey, + systemProgram: new web3.PublicKey("11111111111111111111111111111111"), + }) + .remainingAccounts(remainingAccounts); +}; + +/** + * ### Returns a {@link web3.TransactionInstruction} + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const createGroup = ( + args: CreateGroupArgs, + remainingAccounts: Array = [], +): Promise => + createGroupBuilder(args, remainingAccounts).instruction(); + +/** + * ### Returns a {@link web3.TransactionSignature} + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const createGroupSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: web3.Signer, + }, + }, + remainingAccounts: Array = [], +): Promise => { + const preInstructions: Array = []; + + + return createGroupBuilder({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts) + .preInstructions(preInstructions) + .signers([args.signers.feePayer]) + .rpc(); +} + +export type AddMemberArgs = { + feePayer: web3.PublicKey; + newMember: web3.PublicKey; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link MethodsBuilder} + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const addMemberBuilder = ( + args: AddMemberArgs, + remainingAccounts: Array = [], +): MethodsBuilder => { + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _program.programId); + + return _program + .methods + .addMember( + args.newMember, + args.groupAccountSeedGroupName, + ) + .accounts({ + feePayer: args.feePayer, + groupAccount: groupAccountPubkey, + }) + .remainingAccounts(remainingAccounts); +}; + +/** + * ### Returns a {@link web3.TransactionInstruction} + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const addMember = ( + args: AddMemberArgs, + remainingAccounts: Array = [], +): Promise => + addMemberBuilder(args, remainingAccounts).instruction(); + +/** + * ### Returns a {@link web3.TransactionSignature} + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const addMemberSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: web3.Signer, + }, + }, + remainingAccounts: Array = [], +): Promise => { + const preInstructions: Array = []; + + + return addMemberBuilder({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts) + .preInstructions(preInstructions) + .signers([args.signers.feePayer]) + .rpc(); +} + +export type RemoveMemberArgs = { + feePayer: web3.PublicKey; + member: web3.PublicKey; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link MethodsBuilder} + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const removeMemberBuilder = ( + args: RemoveMemberArgs, + remainingAccounts: Array = [], +): MethodsBuilder => { + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _program.programId); + + return _program + .methods + .removeMember( + args.member, + args.groupAccountSeedGroupName, + ) + .accounts({ + feePayer: args.feePayer, + groupAccount: groupAccountPubkey, + }) + .remainingAccounts(remainingAccounts); +}; + +/** + * ### Returns a {@link web3.TransactionInstruction} + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const removeMember = ( + args: RemoveMemberArgs, + remainingAccounts: Array = [], +): Promise => + removeMemberBuilder(args, remainingAccounts).instruction(); + +/** + * ### Returns a {@link web3.TransactionSignature} + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const removeMemberSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: web3.Signer, + }, + }, + remainingAccounts: Array = [], +): Promise => { + const preInstructions: Array = []; + + + return removeMemberBuilder({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts) + .preInstructions(preInstructions) + .signers([args.signers.feePayer]) + .rpc(); +} + +export type LogPaymentArgs = { + feePayer: web3.PublicKey; + title: string; + participants: web3.PublicKey[]; + amounts: bigint[]; + participationFactors: bigint[]; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link MethodsBuilder} + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const logPaymentBuilder = ( + args: LogPaymentArgs, + remainingAccounts: Array = [], +): MethodsBuilder => { + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _program.programId); + + return _program + .methods + .logPayment( + args.title, + args.participants, + args.amounts.map(e => new BN(e.toString())), + args.participationFactors.map(e => new BN(e.toString())), + args.groupAccountSeedGroupName, + ) + .accounts({ + feePayer: args.feePayer, + groupAccount: groupAccountPubkey, + }) + .remainingAccounts(remainingAccounts); +}; + +/** + * ### Returns a {@link web3.TransactionInstruction} + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const logPayment = ( + args: LogPaymentArgs, + remainingAccounts: Array = [], +): Promise => + logPaymentBuilder(args, remainingAccounts).instruction(); + +/** + * ### Returns a {@link web3.TransactionSignature} + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const logPaymentSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: web3.Signer, + }, + }, + remainingAccounts: Array = [], +): Promise => { + const preInstructions: Array = []; + + + return logPaymentBuilder({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts) + .preInstructions(preInstructions) + .signers([args.signers.feePayer]) + .rpc(); +} + +// Getters + +export const getGroupAccount = ( + publicKey: web3.PublicKey, + commitment?: web3.Commitment +): Promise["groupAccount"]> => _program.account.groupAccount.fetch(publicKey, commitment); diff --git a/examples/solana_anchor/expenses_splitting/program/migrations/deploy.ts b/examples/solana_anchor/expenses_splitting/program/migrations/deploy.ts new file mode 100644 index 0000000..82fb175 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/migrations/deploy.ts @@ -0,0 +1,12 @@ +// Migrations are an early feature. Currently, they're nothing more than this +// single deploy script that's invoked from the CLI, injecting a provider +// configured from the workspace's Anchor.toml. + +const anchor = require("@coral-xyz/anchor"); + +module.exports = async function (provider) { + // Configure client to use the provider. + anchor.setProvider(provider); + + // Add your deploy script here. +}; diff --git a/examples/solana_anchor/expenses_splitting/program/package.json b/examples/solana_anchor/expenses_splitting/program/package.json new file mode 100644 index 0000000..00b41c1 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/package.json @@ -0,0 +1,20 @@ +{ + "scripts": { + "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", + "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" + }, + "dependencies": { + "@coral-xyz/anchor": "^0.29.0" + }, + "devDependencies": { + "chai": "^4.3.4", + "mocha": "^9.0.3", + "chai-as-promised": "^7.1.1", + "ts-mocha": "^10.0.0", + "@types/bn.js": "^5.1.0", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.0.0", + "typescript": "^4.3.5", + "prettier": "^2.6.2" + } +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Cargo.toml b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Cargo.toml new file mode 100644 index 0000000..0589e02 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ExpenseSplitter" +version = "1.0.0" +description = """ +A contract for managing group expenses, allowing members to split costs fairly.""" + +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "ExpenseSplitter" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = "0.29.0" +solana-program = "=1.17.0" +ahash = "=0.8.6" +bumpalo = "=3.14.0" diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Xargo.toml b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Xargo.toml new file mode 100644 index 0000000..b6ef8f6 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/Xargo.toml @@ -0,0 +1,5 @@ +# This file is auto-generated from the CIDL source. +# Editing this file directly is not recommended as it may be overwritten. + +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/constants.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/constants.rs new file mode 100644 index 0000000..fae4535 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/constants.rs @@ -0,0 +1,4 @@ +use anchor_lang::prelude::*; + +#[constant] +pub const SEED: &str = "anchor"; diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/error.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/error.rs new file mode 100644 index 0000000..3d6fbdd --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/error.rs @@ -0,0 +1,18 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. +// +// Docs: https://docs.codigo.ai/c%C3%B3digo-interface-description-language/specification#errors + +use anchor_lang::prelude::*; + +#[error_code] +pub enum ExpenseSplitterError { + #[msg("The sum of balances in the group must always sum to zero.")] + GroupBalanceError, + #[msg("The specified member does not exist in the group.")] + InvalidMemberError, + #[msg("The specified group does not exist.")] + InvalidGroupError, + #[msg("The sum of initial balances does not sum to zero.")] + BalanceMismatchError, +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/add_member.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/add_member.rs new file mode 100644 index 0000000..9b91375 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/add_member.rs @@ -0,0 +1,30 @@ +use crate::*; +use anchor_lang::prelude::*; + +/// Adds a new member to the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - new_member: [Pubkey] Public key of the new member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn handler(ctx: Context, new_member: Pubkey) -> Result<()> { + let group_account = &mut ctx.accounts.group_account; + + assert!( + group_account.members.contains(&group_account.key()), + "only group members can add new members to the group account" + ); + + assert!( + !group_account.members.contains(&new_member), + "provided new member is already a member of this group" + ); + + group_account.members.push(new_member); + group_account.balances.push(0); + + Ok(()) +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/create_group.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/create_group.rs new file mode 100644 index 0000000..fc5c725 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/create_group.rs @@ -0,0 +1,44 @@ +use crate::*; +use anchor_lang::prelude::*; + +/// Creates a new group with initial members and balances. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// 2. `[]` system_program: [AccountInfo] Auto-generated, for account initialization +/// +/// Data: +/// - group_name: [String] The name of the group. +/// - members: [Vec] Array of member public keys. +/// - initial_balances: [Vec] Array of initial balances for each member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn handler( + ctx: Context, + group_name: String, + members: Vec, + initial_balances: Vec, +) -> Result<()> { + assert!( + members.len() > 0, + "members list should provide at least one address" + ); + + assert!( + initial_balances.len() == members.len(), + "members and initial balances lists should be of equal size" + ); + + assert!( + initial_balances.iter().sum::() == 0, + "initial balances should sum zero" + ); + + let group_account = &mut ctx.accounts.group_account; + + group_account.group_name = group_name; + group_account.members = members; + group_account.balances = initial_balances; + + Ok(()) +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/log_payment.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/log_payment.rs new file mode 100644 index 0000000..927d46f --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/log_payment.rs @@ -0,0 +1,89 @@ +use crate::*; +use anchor_lang::prelude::*; + +/// Logs a payment made by or for group members, updating balances accordingly. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - title: [String] Title or description of the payment. +/// - participants: [Vec] Array of participating member public keys. +/// - amounts: [Vec] Array of amounts each participant gave to the group. +/// - participation_factors: [Vec] Array of participation factors for each participant. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn handler( + ctx: Context, + title: String, + participants: Vec, + amounts: Vec, + participation_factors: Vec, +) -> Result<()> { + let group_account = &mut ctx.accounts.group_account; + + assert!( + group_account.members.contains(&group_account.key()), + "only group members can log payments in the group account" + ); + + assert!( + participants.len() > 0, + "participants list should provide at least one address" + ); + assert!( + participants.len() == amounts.len(), + "participants and amounts lists should be of equal size" + ); + assert!( + participants.len() == participation_factors.len(), + "participants and participation factors lists should be of equal size" + ); + + let total_amount: u64 = amounts.iter().sum(); + let total_participation: u64 = participation_factors.iter().sum(); + + assert!( + total_participation > 0, + "participation factors sum should be greater than zero" + ); + + for (participant_idx, participant_address) in participants.iter().enumerate() { + // payment log participant addresses could be in any order, we need to match them with group account members order + let member_idx = group_account + .members + .iter() + .position(|&member_address| member_address == *participant_address) + .expect( + format!( + "participant {} is not a member of this group", + participant_address + ) + .as_str(), + ); + + // high-level formula: member balance += payment - total expenditure * participation + let expenditure = + total_amount * participation_factors[participant_idx] / total_participation; + + group_account.balances[member_idx] += (amounts[participant_idx] - expenditure) as i64; + + msg!( + "Group '{}' payment '{}' of {:?}. Member {} spended {:?} and paid {:?}", + group_account.group_name, + title, + total_amount, + participant_address, + expenditure, + amounts[participant_idx] + ); + } + + // just in case + assert!( + group_account.balances.iter().sum::() == 0, + "group balances should sum zero, something went wrong" + ); + + Ok(()) +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/mod.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/mod.rs new file mode 100644 index 0000000..d972675 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/mod.rs @@ -0,0 +1,12 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +pub mod create_group; +pub mod add_member; +pub mod remove_member; +pub mod log_payment; + +pub use create_group::*; +pub use add_member::*; +pub use remove_member::*; +pub use log_payment::*; diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/remove_member.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/remove_member.rs new file mode 100644 index 0000000..7e350a2 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/instructions/remove_member.rs @@ -0,0 +1,36 @@ +use crate::*; +use anchor_lang::prelude::*; + +/// Removes a member from the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - member: [Pubkey] Public key of the member to remove. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn handler(ctx: Context, member: Pubkey) -> Result<()> { + let group_account = &mut ctx.accounts.group_account; + + assert!( + group_account.members.contains(&group_account.key()), + "only group members can remove members from the group account" + ); + + let member_idx = group_account + .members + .iter() + .position(|&address| address == member) + .expect("could not find member to remove within this group"); + + assert!( + group_account.balances[member_idx] == 0, + "member balance should be zero before removal" + ); + + group_account.members.remove(member_idx); + group_account.balances.remove(member_idx); + + Ok(()) +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/lib.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/lib.rs new file mode 100644 index 0000000..b0b467d --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/lib.rs @@ -0,0 +1,177 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +pub mod constants; +pub mod error; +pub mod instructions; +pub mod state; + +use anchor_lang::prelude::*; +use std::str::FromStr; + +pub use constants::*; +pub use instructions::*; +pub use state::*; + +declare_id!("CBdZHhteKeEySFipeE9w2zRsigs3CsGyFEQ9UKwEpfJ5"); + +#[program] +pub mod ExpenseSplitter { + use super::*; + +/// Creates a new group with initial members and balances. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// 2. `[]` system_program: [AccountInfo] Auto-generated, for account initialization +/// +/// Data: +/// - group_name: [String] The name of the group. +/// - members: [Vec] Array of member public keys. +/// - initial_balances: [Vec] Array of initial balances for each member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn create_group(ctx: Context, group_name: String, members: Vec, initial_balances: Vec, _group_account_seed_group_name: String) -> Result<()> { + create_group::handler(ctx, group_name, members, initial_balances) + } + +/// Adds a new member to the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - new_member: [Pubkey] Public key of the new member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn add_member(ctx: Context, new_member: Pubkey, _group_account_seed_group_name: String) -> Result<()> { + add_member::handler(ctx, new_member) + } + +/// Removes a member from the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - member: [Pubkey] Public key of the member to remove. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn remove_member(ctx: Context, member: Pubkey, _group_account_seed_group_name: String) -> Result<()> { + remove_member::handler(ctx, member) + } + +/// Logs a payment made by or for group members, updating balances accordingly. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - title: [String] Title or description of the payment. +/// - participants: [Vec] Array of participating member public keys. +/// - amounts: [Vec] Array of amounts each participant gave to the group. +/// - participation_factors: [Vec] Array of participation factors for each participant. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn log_payment(ctx: Context, title: String, participants: Vec, amounts: Vec, participation_factors: Vec, _group_account_seed_group_name: String) -> Result<()> { + log_payment::handler(ctx, title, participants, amounts, participation_factors) + } + + + #[derive(Accounts)] + #[instruction( + group_name: String, + members: Vec, + initial_balances: Vec, + group_account_seed_group_name: String, + )] + pub struct CreateGroup<'info> { + #[account( + mut, + owner=Pubkey::from_str("11111111111111111111111111111111").unwrap(), + )] + pub fee_payer: Signer<'info>, + + #[account( + init, + space=4120, + payer=fee_payer, + seeds = [ + group_account_seed_group_name.as_bytes().as_ref(), + ], + bump, + )] + pub group_account: Account<'info, GroupAccount>, + + pub system_program: Program<'info, System>, + } + + #[derive(Accounts)] + #[instruction( + new_member: Pubkey, + group_account_seed_group_name: String, + )] + pub struct AddMember<'info> { + #[account( + mut, + owner=Pubkey::from_str("11111111111111111111111111111111").unwrap(), + )] + pub fee_payer: Signer<'info>, + + #[account( + mut, + seeds = [ + group_account_seed_group_name.as_bytes().as_ref(), + ], + bump, + )] + pub group_account: Account<'info, GroupAccount>, + } + + #[derive(Accounts)] + #[instruction( + member: Pubkey, + group_account_seed_group_name: String, + )] + pub struct RemoveMember<'info> { + #[account( + mut, + owner=Pubkey::from_str("11111111111111111111111111111111").unwrap(), + )] + pub fee_payer: Signer<'info>, + + #[account( + mut, + seeds = [ + group_account_seed_group_name.as_bytes().as_ref(), + ], + bump, + )] + pub group_account: Account<'info, GroupAccount>, + } + + #[derive(Accounts)] + #[instruction( + title: String, + participants: Vec, + amounts: Vec, + participation_factors: Vec, + group_account_seed_group_name: String, + )] + pub struct LogPayment<'info> { + #[account( + mut, + owner=Pubkey::from_str("11111111111111111111111111111111").unwrap(), + )] + pub fee_payer: Signer<'info>, + + #[account( + mut, + seeds = [ + group_account_seed_group_name.as_bytes().as_ref(), + ], + bump, + )] + pub group_account: Account<'info, GroupAccount>, + } +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/group_account.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/group_account.rs new file mode 100644 index 0000000..feb24ab --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/group_account.rs @@ -0,0 +1,11 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use anchor_lang::prelude::*; + +#[account] +pub struct GroupAccount { + pub group_name: String, + pub members: Vec, + pub balances: Vec, +} diff --git a/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/mod.rs b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/mod.rs new file mode 100644 index 0000000..45eb81c --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/programs/ExpenseSplitter/src/state/mod.rs @@ -0,0 +1,8 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use anchor_lang::prelude::*; + +pub mod group_account; + +pub use group_account::*; diff --git a/examples/solana_anchor/expenses_splitting/program/tests/expense_splitter.ts b/examples/solana_anchor/expenses_splitting/program/tests/expense_splitter.ts new file mode 100644 index 0000000..b7e6250 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/tests/expense_splitter.ts @@ -0,0 +1,22 @@ + +import { AnchorProvider, BN, setProvider, web3 } from "@coral-xyz/anchor"; +import * as expenseSplitterClient from "../app/program_client"; +import chai from "chai"; +import { assert, expect } from "chai"; +import chaiAsPromised from "chai-as-promised"; +import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; +chai.use(chaiAsPromised); + +const programId = new web3.PublicKey("CBdZHhteKeEySFipeE9w2zRsigs3CsGyFEQ9UKwEpfJ5"); + +describe("expense_splitter tests", () => { + // Configure the client to use the local cluster + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const systemWallet = (provider.wallet as NodeWallet).payer; + + it("First test", async () => { + // Add your test here + }); +}); diff --git a/examples/solana_anchor/expenses_splitting/program/tsconfig.json b/examples/solana_anchor/expenses_splitting/program/tsconfig.json new file mode 100644 index 0000000..b7b6454 --- /dev/null +++ b/examples/solana_anchor/expenses_splitting/program/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "resolveJsonModule": true + } +} diff --git a/examples/solana_native/expenses_splitting/README.md b/examples/solana_native/expenses_splitting/README.md new file mode 100644 index 0000000..87e8f5a --- /dev/null +++ b/examples/solana_native/expenses_splitting/README.md @@ -0,0 +1,67 @@ +# Expenses splitting + +This program does expenses splitting on Solana. The program manages group accounts. +Anyone can create a group by providing addresses and initial balances. The group account holds balances for each of its members. +The sum of balances of a group always sum zero. +Members can log payments by providing addresses, payments and participation factors. +Group members can add and remove members. + +# Program template + +Template that includes the CIDL, and the generated code with its business logic. + +Creates a user, and tracks the income and outcome amount per user + +## Generate code + +```shell +codigo solana generate cidl.yaml +``` + +## Build contract + +From within the program directory + +```shell +cargo build-sbf +``` + +## Run solana validator + +From another terminal (keep it open after running the command) + +```shell +solana-test-validator +``` + +## Deploy to validator + +From within the program directory + +```shell +solana program deploy target/deploy/validate_expenses_splitting.so --program-id ../expenses_splitting_kp.josn +``` + +## Install program client dependencies + +From within the program_client directory + +```shell +npm install +``` + +## Install ts-node for running `app.ts` + +From within the program_client directory + +```shell +npm install --save-dev ts-node +``` + +## Run client + +From within the program_client directory + +```shell +npx ts-node app.ts +``` diff --git a/examples/solana_native/expenses_splitting/cidl.yaml b/examples/solana_native/expenses_splitting/cidl.yaml new file mode 100644 index 0000000..9e15978 --- /dev/null +++ b/examples/solana_native/expenses_splitting/cidl.yaml @@ -0,0 +1,112 @@ +cidl: "0.9" +info: + name: ExpenseSplitter + title: Expense Splitter Contract + version: "1.0.0" + summary: A contract for managing group expenses, allowing members to split costs fairly. + contact: + name: "Developer Name" + web: "https://developerwebsite.com" + email: "contact@developerwebsite.com" + git: "https://github.com/developer/ExpenseSplitter" + license: + name: "MIT License" + identifier: "MIT" + url: "https://opensource.org/licenses/MIT" + +solana: + seeds: + groupAccount: + items: + - name: "group_name" + type: "string" + +types: + GroupAccount: + summary: Represents a group account with members and their balances. + fields: + - name: "group_name" + type: "string" + description: "The name of the group." + attributes: [cap=100] + - name: "members" + type: "array" + description: "Array of member public keys." + attributes: [cap=100] + - name: "balances" + type: "array" + description: "Array of member balances in lamports." + attributes: [cap=100] + +methods: + - name: "createGroup" + summary: "Creates a new group with initial members and balances." + inputs: + - name: "group_account" + type: sol:account + attributes: [ sol:init ] + - name: "group_name" + type: "string" + description: "The name of the group." + - name: "members" + type: "array" + description: "Array of member public keys." + attributes: [cap=100] + - name: "initial_balances" + type: "array" + description: "Array of initial balances for each member." + attributes: [cap=100] + solana: + default-payer: true + + - name: "addMember" + summary: "Adds a new member to the group." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "new_member" + type: "sol:pubkey" + description: "Public key of the new member." + + - name: "removeMember" + summary: "Removes a member from the group." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "member" + type: "sol:pubkey" + description: "Public key of the member to remove." + + - name: "logPayment" + summary: "Logs a payment made by or for group members, updating balances accordingly." + inputs: + - name: "group_account" + type: sol:account + attributes: [sol:writable] + - name: "title" + type: "string" + description: "Title or description of the payment." + - name: "participants" + type: "array" + description: "Array of participating member public keys." + attributes: [cap=100] + - name: "amounts" + type: "array" + description: "Array of amounts each participant gave to the group." + attributes: [cap=100] + - name: "participation_factors" + type: "array" + description: "Array of participation factors for each participant." + attributes: [cap=100] + +errors: + - id: "GroupBalanceError" + msg: "The sum of balances in the group must always sum to zero." + - id: "InvalidMemberError" + msg: "The specified member does not exist in the group." + - id: "InvalidGroupError" + msg: "The specified group does not exist." + - id: "BalanceMismatchError" + msg: "The sum of initial balances does not sum to zero." \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/Cargo.lock b/examples/solana_native/expenses_splitting/program/Cargo.lock new file mode 100644 index 0000000..e787227 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/Cargo.lock @@ -0,0 +1,1652 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.12", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom 0.2.12", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "expense_splitter" +version = "1.0.0" +dependencies = [ + "ahash 0.8.6", + "borsh 0.10.3", + "num-derive 0.4.2", + "num-traits", + "num_enum", + "solana-program", + "thiserror", + "toml_edit", +] + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.6", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "solana-frozen-abi" +version = "1.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2c5e5dde22cac045d29675b3fefa84817e1f63b0b911d094c599e80c0c07d9" +dependencies = [ + "ahash 0.8.6", + "blake3", + "block-buffer 0.10.4", + "bs58", + "bv", + "byteorder", + "cc", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "296e4cf0e2479e4c21afe4d17e32526f71f1bcd93b1c7c660900bc3e4233447a" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.52", +] + +[[package]] +name = "solana-program" +version = "1.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a3b9623f09e2c480b4e129c92d7a036f8614fd0fc7519791bd44e64061ce8" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags 2.4.2", + "blake3", + "borsh 0.10.3", + "borsh 0.9.3", + "bs58", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.12", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint", + "num-derive 0.3.3", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.52", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] diff --git a/examples/solana_native/expenses_splitting/program/Cargo.toml b/examples/solana_native/expenses_splitting/program/Cargo.toml new file mode 100644 index 0000000..b6f648e --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/Cargo.toml @@ -0,0 +1,30 @@ + +[package] +name = "expense_splitter" +version = "1.0.0" +edition = "2021" +license = "mit_license" +publish = false + +authors = ["Developer Name "] +description = """ +A contract for managing group expenses, allowing members to split costs fairly. +""" +homepage = "https://developerwebsite.com" +repository = "https://github.com/developer/ExpenseSplitter" + +[dependencies] +solana-program = ">=1.16, <1.18" +borsh = "0.10.3" +thiserror = "1" +num-derive = "0.4" +num-traits = "0.2" +num_enum = "0.7.0" +ahash = "=0.8.6" +toml_edit = "=0.21.0" + + + +[lib] +crate-type = ["cdylib", "lib"] +path = "./lib.rs" \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/entrypoint.rs b/examples/solana_native/expenses_splitting/program/generated/entrypoint.rs new file mode 100644 index 0000000..9f3ea76 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/entrypoint.rs @@ -0,0 +1,24 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use crate::generated::errors::ExpenseSplitterError; +use crate::generated::processor::Processor; +use solana_program::program_error::PrintProgramError; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + if let Err(error) = Processor::process(program_id, accounts, data) { + // catch the error so we can print it + error.print::(); + return Err(error); + } + Ok(()) +} \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/errors.rs b/examples/solana_native/expenses_splitting/program/generated/errors.rs new file mode 100644 index 0000000..91b6021 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/errors.rs @@ -0,0 +1,82 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use num_derive::FromPrimitive; +use solana_program::decode_error::DecodeError; +use solana_program::msg; +use solana_program::program_error::{PrintProgramError, ProgramError}; +use thiserror::Error; + +#[derive(Error, FromPrimitive, Debug, Clone)] +pub enum ExpenseSplitterError { + #[error("Invalid Instruction")] + InvalidInstruction, + + #[error("Invalid Signer Permission")] + InvalidSignerPermission, + + #[error("Not The Expected Account Address")] + NotExpectedAddress, + + #[error("Wrong Account Owner")] + WrongAccountOwner, + + #[error("Invalid Account Len")] + InvalidAccountLen, + + #[error("Executable Account Expected")] + ExecutableAccountExpected, + + #[error("Account Already Closed")] + AccountAlreadyClosed, + + #[error("GroupBalanceError")] + GroupBalanceError + #[error("InvalidMemberError")] + InvalidMemberError + #[error("InvalidGroupError")] + InvalidGroupError + #[error("BalanceMismatchError")] + BalanceMismatchError + +} + +impl From for ProgramError { + fn from(e: ExpenseSplitterError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl DecodeError for ExpenseSplitterError { + fn type_of() -> &'static str { + "ExpenseSplitterError" + } +} + +impl PrintProgramError for ExpenseSplitterError { + fn print(&self) + where + E: 'static + + std::error::Error + + DecodeError + + PrintProgramError + + num_traits::FromPrimitive, + { + match self { + ExpenseSplitterError::InvalidInstruction => msg!("Error: Invalid instruction"), + ExpenseSplitterError::InvalidSignerPermission => msg!("Error: The account is_signer value is not the expected one"), + ExpenseSplitterError::NotExpectedAddress => { + msg!("Error: Not the expected account address") + } + ExpenseSplitterError::WrongAccountOwner => msg!("Error: Wrong account owner"), + ExpenseSplitterError::InvalidAccountLen => msg!("Error: Invalid account length"), + ExpenseSplitterError::ExecutableAccountExpected => msg!("Error: Executable account expected"), + ExpenseSplitterError::AccountAlreadyClosed => msg!("Error: Account Already Closed"), + ExpenseSplitterError::GroupBalanceError => msg!("Error: The sum of balances in the group must always sum to zero."), + ExpenseSplitterError::InvalidMemberError => msg!("Error: The specified member does not exist in the group."), + ExpenseSplitterError::InvalidGroupError => msg!("Error: The specified group does not exist."), + ExpenseSplitterError::BalanceMismatchError => msg!("Error: The sum of initial balances does not sum to zero."), + + } + } +} \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/instructions.rs b/examples/solana_native/expenses_splitting/program/generated/instructions.rs new file mode 100644 index 0000000..2c849b1 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/instructions.rs @@ -0,0 +1,104 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; +use solana_program::pubkey::Pubkey; +use crate::generated::errors::ExpenseSplitterError; + +#[derive(BorshSerialize, Debug)] +pub enum ExpenseSplitterInstruction { +/// Creates a new group with initial members and balances. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// 2. `[]` system_program: [AccountInfo] Auto-generated, for account initialization +/// +/// Data: +/// - group_name: [String] The name of the group. +/// - members: [Vec] Array of member public keys. +/// - initial_balances: [Vec] Array of initial balances for each member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + CreateGroup(CreateGroupArgs), + +/// Adds a new member to the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - new_member: [Pubkey] Public key of the new member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + AddMember(AddMemberArgs), + +/// Removes a member from the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - member: [Pubkey] Public key of the member to remove. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + RemoveMember(RemoveMemberArgs), + +/// Logs a payment made by or for group members, updating balances accordingly. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - title: [String] Title or description of the payment. +/// - participants: [Vec] Array of participating member public keys. +/// - amounts: [Vec] Array of amounts each participant gave to the group. +/// - participation_factors: [Vec] Array of participation factors for each participant. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + LogPayment(LogPaymentArgs), + +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateGroupArgs { + pub group_name: String, + pub members: Vec, + pub initial_balances: Vec, + pub group_account_seed_group_name: String, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct AddMemberArgs { + pub new_member: Pubkey, + pub group_account_seed_group_name: String, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct RemoveMemberArgs { + pub member: Pubkey, + pub group_account_seed_group_name: String, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct LogPaymentArgs { + pub title: String, + pub participants: Vec, + pub amounts: Vec, + pub participation_factors: Vec, + pub group_account_seed_group_name: String, +} + +impl ExpenseSplitterInstruction { + pub fn unpack(input: &[u8]) -> Result { + let (&variant, rest) = input.split_first().ok_or(ExpenseSplitterError::InvalidInstruction)?; + + Ok(match variant { + 0 => Self::CreateGroup(CreateGroupArgs::try_from_slice(rest).unwrap()), + 1 => Self::AddMember(AddMemberArgs::try_from_slice(rest).unwrap()), + 2 => Self::RemoveMember(RemoveMemberArgs::try_from_slice(rest).unwrap()), + 3 => Self::LogPayment(LogPaymentArgs::try_from_slice(rest).unwrap()), + _ => return Err(ExpenseSplitterError::InvalidInstruction.into()) + }) + } +} \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/mod.rs b/examples/solana_native/expenses_splitting/program/generated/mod.rs new file mode 100644 index 0000000..2620865 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/mod.rs @@ -0,0 +1,9 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + + +pub mod entrypoint; +pub mod errors; +pub mod instructions; +pub mod processor; +pub mod state; \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/processor.rs b/examples/solana_native/expenses_splitting/program/generated/processor.rs new file mode 100644 index 0000000..654b19a --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/processor.rs @@ -0,0 +1,399 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use std::str::FromStr; +use std::ops::DerefMut; +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::borsh0_10::try_from_slice_unchecked; +use solana_program::account_info::{AccountInfo, next_account_info, next_account_infos}; +use solana_program::entrypoint::ProgramResult; +use solana_program::program::{invoke, invoke_signed}; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; +use solana_program::system_instruction::create_account; +use solana_program::{msg, system_program}; +use solana_program::sysvar::Sysvar; +use solana_program::program_pack::Pack; +use crate::generated::errors::ExpenseSplitterError; +use crate::generated::instructions::ExpenseSplitterInstruction; + +use crate::generated::state::{ + Account, + AccountPDA, + GroupAccount, +}; +use crate::src::*; + +pub struct Processor; + +impl Processor { + pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], + ) -> ProgramResult { + let instruction = ExpenseSplitterInstruction::unpack(data)?; + + match instruction { + ExpenseSplitterInstruction::CreateGroup(args) => { + msg!("Instruction: CreateGroup"); + Self::process_create_group( + program_id, + accounts, + args.group_name, + args.members, + args.initial_balances, + args.group_account_seed_group_name, + ) + } + ExpenseSplitterInstruction::AddMember(args) => { + msg!("Instruction: AddMember"); + Self::process_add_member( + program_id, + accounts, + args.new_member, + args.group_account_seed_group_name, + ) + } + ExpenseSplitterInstruction::RemoveMember(args) => { + msg!("Instruction: RemoveMember"); + Self::process_remove_member( + program_id, + accounts, + args.member, + args.group_account_seed_group_name, + ) + } + ExpenseSplitterInstruction::LogPayment(args) => { + msg!("Instruction: LogPayment"); + Self::process_log_payment( + program_id, + accounts, + args.title, + args.participants, + args.amounts, + args.participation_factors, + args.group_account_seed_group_name, + ) + } + } + } + +/// Creates a new group with initial members and balances. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// 2. `[]` system_program: [AccountInfo] Auto-generated, for account initialization +/// +/// Data: +/// - group_name: [String] The name of the group. +/// - members: [Vec] Array of member public keys. +/// - initial_balances: [Vec] Array of initial balances for each member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn process_create_group( + program_id: &Pubkey, + accounts: &[AccountInfo], + group_name: String, + members: Vec, + initial_balances: Vec, + group_account_seed_group_name: String, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let fee_payer_info = next_account_info(account_info_iter)?; + let group_account_info = next_account_info(account_info_iter)?; + let system_program_info = next_account_info(account_info_iter)?; + + // Derive PDAs + let (group_account_pubkey, group_account_bump) = Pubkey::find_program_address( + &[group_account_seed_group_name.as_bytes().as_ref()], + program_id, + ); + + // Security Checks + if fee_payer_info.is_signer != true { + return Err(ExpenseSplitterError::InvalidSignerPermission.into()); + } + + if *group_account_info.key != group_account_pubkey { + return Err(ExpenseSplitterError::NotExpectedAddress.into()); + } + + if *system_program_info.key != Pubkey::from_str("11111111111111111111111111111111").unwrap() { + return Err(ExpenseSplitterError::NotExpectedAddress.into()); + } + + + // Accounts Initializations + let space: usize = 4112; + let rent = Rent::get()?; + let rent_minimum_balance = rent.minimum_balance(space); + + invoke_signed( + &create_account( + &fee_payer_info.key, + &group_account_info.key, + rent_minimum_balance, + space as u64, + program_id, + ), + &[fee_payer_info.clone(), group_account_info.clone()], + &[&[group_account_seed_group_name.as_bytes().as_ref(), &[group_account_bump]]], + )?; + + + // Security Checks + if *fee_payer_info.owner != Pubkey::from_str("11111111111111111111111111111111").unwrap() { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if *group_account_info.owner != *program_id { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if group_account_info.data_len() != 4112usize { + return Err(ExpenseSplitterError::InvalidAccountLen.into()); + } + + + // Accounts Deserialization + let group_account = &mut AccountPDA::new( + &group_account_info, + try_from_slice_unchecked::(&group_account_info.data.borrow()).unwrap(), + group_account_bump, + ); + + // Calling STUB + create_group::create_group( + program_id, + group_account, + group_name, + members, + initial_balances, + )?; + + // Accounts Serialization + group_account.data.serialize(&mut &mut group_account_info.data.borrow_mut()[..])?; + + Ok(()) + } + +/// Adds a new member to the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - new_member: [Pubkey] Public key of the new member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn process_add_member( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_member: Pubkey, + group_account_seed_group_name: String, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let fee_payer_info = next_account_info(account_info_iter)?; + let group_account_info = next_account_info(account_info_iter)?; + + // Derive PDAs + let (group_account_pubkey, group_account_bump) = Pubkey::find_program_address( + &[group_account_seed_group_name.as_bytes().as_ref()], + program_id, + ); + + // Security Checks + if fee_payer_info.is_signer != true { + return Err(ExpenseSplitterError::InvalidSignerPermission.into()); + } + + if *group_account_info.key != group_account_pubkey { + return Err(ExpenseSplitterError::NotExpectedAddress.into()); + } + + + + // Security Checks + if *fee_payer_info.owner != Pubkey::from_str("11111111111111111111111111111111").unwrap() { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if *group_account_info.owner != *program_id { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if group_account_info.data_len() != 4112usize { + return Err(ExpenseSplitterError::InvalidAccountLen.into()); + } + + + // Accounts Deserialization + let group_account = &mut AccountPDA::new( + &group_account_info, + try_from_slice_unchecked::(&group_account_info.data.borrow()).unwrap(), + group_account_bump, + ); + + // Calling STUB + add_member::add_member( + program_id, + group_account, + new_member, + )?; + + // Accounts Serialization + group_account.data.serialize(&mut &mut group_account_info.data.borrow_mut()[..])?; + + Ok(()) + } + +/// Removes a member from the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - member: [Pubkey] Public key of the member to remove. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn process_remove_member( + program_id: &Pubkey, + accounts: &[AccountInfo], + member: Pubkey, + group_account_seed_group_name: String, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let fee_payer_info = next_account_info(account_info_iter)?; + let group_account_info = next_account_info(account_info_iter)?; + + // Derive PDAs + let (group_account_pubkey, group_account_bump) = Pubkey::find_program_address( + &[group_account_seed_group_name.as_bytes().as_ref()], + program_id, + ); + + // Security Checks + if fee_payer_info.is_signer != true { + return Err(ExpenseSplitterError::InvalidSignerPermission.into()); + } + + if *group_account_info.key != group_account_pubkey { + return Err(ExpenseSplitterError::NotExpectedAddress.into()); + } + + + + // Security Checks + if *fee_payer_info.owner != Pubkey::from_str("11111111111111111111111111111111").unwrap() { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if *group_account_info.owner != *program_id { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if group_account_info.data_len() != 4112usize { + return Err(ExpenseSplitterError::InvalidAccountLen.into()); + } + + + // Accounts Deserialization + let group_account = &mut AccountPDA::new( + &group_account_info, + try_from_slice_unchecked::(&group_account_info.data.borrow()).unwrap(), + group_account_bump, + ); + + // Calling STUB + remove_member::remove_member( + program_id, + group_account, + member, + )?; + + // Accounts Serialization + group_account.data.serialize(&mut &mut group_account_info.data.borrow_mut()[..])?; + + Ok(()) + } + +/// Logs a payment made by or for group members, updating balances accordingly. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - title: [String] Title or description of the payment. +/// - participants: [Vec] Array of participating member public keys. +/// - amounts: [Vec] Array of amounts each participant gave to the group. +/// - participation_factors: [Vec] Array of participation factors for each participant. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + pub fn process_log_payment( + program_id: &Pubkey, + accounts: &[AccountInfo], + title: String, + participants: Vec, + amounts: Vec, + participation_factors: Vec, + group_account_seed_group_name: String, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let fee_payer_info = next_account_info(account_info_iter)?; + let group_account_info = next_account_info(account_info_iter)?; + + // Derive PDAs + let (group_account_pubkey, group_account_bump) = Pubkey::find_program_address( + &[group_account_seed_group_name.as_bytes().as_ref()], + program_id, + ); + + // Security Checks + if fee_payer_info.is_signer != true { + return Err(ExpenseSplitterError::InvalidSignerPermission.into()); + } + + if *group_account_info.key != group_account_pubkey { + return Err(ExpenseSplitterError::NotExpectedAddress.into()); + } + + + + // Security Checks + if *fee_payer_info.owner != Pubkey::from_str("11111111111111111111111111111111").unwrap() { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if *group_account_info.owner != *program_id { + return Err(ExpenseSplitterError::WrongAccountOwner.into()); + } + + if group_account_info.data_len() != 4112usize { + return Err(ExpenseSplitterError::InvalidAccountLen.into()); + } + + + // Accounts Deserialization + let group_account = &mut AccountPDA::new( + &group_account_info, + try_from_slice_unchecked::(&group_account_info.data.borrow()).unwrap(), + group_account_bump, + ); + + // Calling STUB + log_payment::log_payment( + program_id, + group_account, + title, + participants, + amounts, + participation_factors, + )?; + + // Accounts Serialization + group_account.data.serialize(&mut &mut group_account_info.data.borrow_mut()[..])?; + + Ok(()) + } +} \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/generated/state.rs b/examples/solana_native/expenses_splitting/program/generated/state.rs new file mode 100644 index 0000000..d6800ad --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/generated/state.rs @@ -0,0 +1,40 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +#[derive(Clone, Debug)] +pub struct Account<'a, 'b, T> { + pub data: T, + pub info: &'a AccountInfo<'b>, +} + +#[derive(Clone, Debug)] +pub struct AccountPDA<'a, 'b, T> { + pub data: T, + pub info: &'a AccountInfo<'b>, + pub bump: u8, +} + +impl<'a, 'b, T> Account<'a, 'b, T> { + pub fn new(info: &'a AccountInfo<'b>, data: T) -> Self { + Self { data, info } + } +} + +impl<'a, 'b, T> AccountPDA<'a, 'b, T> { + pub fn new(info: &'a AccountInfo<'b>, data: T, bump: u8) -> Self { + Self { data, info, bump } + } +} + +/// Represents a group account with members and their balances. +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Default)] +pub struct GroupAccount { + pub group_name: String, + pub members: Vec, + pub balances: Vec, +} + diff --git a/examples/solana_native/expenses_splitting/program/lib.rs b/examples/solana_native/expenses_splitting/program/lib.rs new file mode 100644 index 0000000..df02c1d --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/lib.rs @@ -0,0 +1,5 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +pub mod generated; +pub mod src; \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program/src/add_member.rs b/examples/solana_native/expenses_splitting/program/src/add_member.rs new file mode 100644 index 0000000..531ff36 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/src/add_member.rs @@ -0,0 +1,34 @@ +use solana_program::entrypoint::ProgramResult; +use solana_program::pubkey::Pubkey; + +use crate::generated::state::{AccountPDA, GroupAccount}; + +/// Adds a new member to the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - new_member: [Pubkey] Public key of the new member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn add_member( + program_id: &Pubkey, + group_account: &mut AccountPDA, + new_member: Pubkey, +) -> ProgramResult { + assert!( + group_account.data.members.contains(group_account.info.key), + "only group members can add new members to the group account" + ); + + assert!( + !group_account.data.members.contains(&new_member), + "provided new member is already a member of this group" + ); + + group_account.data.members.push(new_member); + group_account.data.balances.push(0); + + Ok(()) +} diff --git a/examples/solana_native/expenses_splitting/program/src/create_group.rs b/examples/solana_native/expenses_splitting/program/src/create_group.rs new file mode 100644 index 0000000..35c6970 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/src/create_group.rs @@ -0,0 +1,45 @@ +use solana_program::entrypoint::ProgramResult; +use solana_program::pubkey::Pubkey; + +use crate::generated::state::{AccountPDA, GroupAccount}; + +/// Creates a new group with initial members and balances. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// 2. `[]` system_program: [AccountInfo] Auto-generated, for account initialization +/// +/// Data: +/// - group_name: [String] The name of the group. +/// - members: [Vec] Array of member public keys. +/// - initial_balances: [Vec] Array of initial balances for each member. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn create_group( + program_id: &Pubkey, + group_account: &mut AccountPDA, + group_name: String, + members: Vec, + initial_balances: Vec, +) -> ProgramResult { + assert!( + members.len() > 0, + "members list should provide at least one address" + ); + + assert!( + initial_balances.len() == members.len(), + "members and initial balances lists should be of equal size" + ); + + assert!( + initial_balances.iter().sum::() == 0, + "initial balances should sum zero" + ); + + group_account.data.group_name = group_name; + group_account.data.members = members; + group_account.data.balances = initial_balances; + + Ok(()) +} diff --git a/examples/solana_native/expenses_splitting/program/src/log_payment.rs b/examples/solana_native/expenses_splitting/program/src/log_payment.rs new file mode 100644 index 0000000..44fd6f0 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/src/log_payment.rs @@ -0,0 +1,92 @@ +use solana_program::entrypoint::ProgramResult; +use solana_program::msg; +use solana_program::pubkey::Pubkey; + +use crate::generated::state::{AccountPDA, GroupAccount}; + +/// Logs a payment made by or for group members, updating balances accordingly. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - title: [String] Title or description of the payment. +/// - participants: [Vec] Array of participating member public keys. +/// - amounts: [Vec] Array of amounts each participant gave to the group. +/// - participation_factors: [Vec] Array of participation factors for each participant. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn log_payment( + program_id: &Pubkey, + group_account: &mut AccountPDA, + title: String, + participants: Vec, + amounts: Vec, + participation_factors: Vec, +) -> ProgramResult { + assert!( + group_account.data.members.contains(group_account.info.key), + "only group members can log payments in the group account" + ); + + assert!( + participants.len() > 0, + "participants list should provide at least one address" + ); + assert!( + participants.len() == amounts.len(), + "participants and amounts lists should be of equal size" + ); + assert!( + participants.len() == participation_factors.len(), + "participants and participation factors lists should be of equal size" + ); + + let total_amount: u64 = amounts.iter().sum(); + let total_participation: u64 = participation_factors.iter().sum(); + + assert!( + total_participation > 0, + "participation factors sum should be greater than zero" + ); + + for (participant_idx, participant_address) in participants.iter().enumerate() { + // payment log participant addresses could be in any order, we need to match them with group account members order + let member_idx = group_account + .data + .members + .iter() + .position(|&member_address| member_address == *participant_address) + .expect( + format!( + "participant {} is not a member of this group", + participant_address + ) + .as_str(), + ); + + // high-level formula: member balance += payment - total expenditure * participation + let expenditure = + total_amount * participation_factors[participant_idx] / total_participation; + + group_account.data.balances[member_idx] += (amounts[participant_idx] - expenditure) as i64; + + msg!( + "Group '{}' payment '{}' of {:?}. Member {} spended {:?} and paid {:?}", + group_account.data.group_name, + title, + total_amount, + participant_address, + expenditure, + amounts[participant_idx] + ); + } + + // just in case + assert!( + group_account.data.balances.iter().sum::() == 0, + "group balances should sum zero, something went wrong" + ); + + Ok(()) +} diff --git a/examples/solana_native/expenses_splitting/program/src/mod.rs b/examples/solana_native/expenses_splitting/program/src/mod.rs new file mode 100644 index 0000000..136d25a --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/src/mod.rs @@ -0,0 +1,7 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +pub mod create_group; +pub mod add_member; +pub mod remove_member; +pub mod log_payment; diff --git a/examples/solana_native/expenses_splitting/program/src/remove_member.rs b/examples/solana_native/expenses_splitting/program/src/remove_member.rs new file mode 100644 index 0000000..4e33938 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program/src/remove_member.rs @@ -0,0 +1,41 @@ +use solana_program::entrypoint::ProgramResult; +use solana_program::pubkey::Pubkey; + +use crate::generated::state::{AccountPDA, GroupAccount}; + +/// Removes a member from the group. +/// +/// Accounts: +/// 0. `[writable, signer]` fee_payer: [AccountInfo] Auto-generated, default fee payer +/// 1. `[writable]` group_account: [GroupAccount] +/// +/// Data: +/// - member: [Pubkey] Public key of the member to remove. +/// - group_account_seed_group_name: [String] Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" +pub fn remove_member( + program_id: &Pubkey, + group_account: &mut AccountPDA, + member: Pubkey, +) -> ProgramResult { + assert!( + group_account.data.members.contains(group_account.info.key), + "only group members can remove members from the group account" + ); + + let member_idx = group_account + .data + .members + .iter() + .position(|&address| address == member) + .expect("could not find member to remove within this group"); + + assert!( + group_account.data.balances[member_idx] == 0, + "member balance should be zero before removal" + ); + + group_account.data.members.remove(member_idx); + group_account.data.balances.remove(member_idx); + + Ok(()) +} diff --git a/examples/solana_native/expenses_splitting/program_client/index.ts b/examples/solana_native/expenses_splitting/program_client/index.ts new file mode 100644 index 0000000..674398a --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +export * from "./lib/pda"; +export * from "./lib/rpc"; +export * from "./lib/types"; diff --git a/examples/solana_native/expenses_splitting/program_client/lib/pda.ts b/examples/solana_native/expenses_splitting/program_client/lib/pda.ts new file mode 100644 index 0000000..81ee21f --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/lib/pda.ts @@ -0,0 +1,21 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +import {PublicKey} from "@solana/web3.js"; + +export type GroupAccountSeeds = { + groupName: string, +}; + +export const deriveGroupAccountPDA = ( + seeds: GroupAccountSeeds, + programId: PublicKey +): [PublicKey, number] => { + return PublicKey.findProgramAddressSync( + [ + Buffer.from(seeds.groupName, "utf8"), + ], + programId, + ) +}; + diff --git a/examples/solana_native/expenses_splitting/program_client/lib/rpc.ts b/examples/solana_native/expenses_splitting/program_client/lib/rpc.ts new file mode 100644 index 0000000..8eddf25 --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/lib/rpc.ts @@ -0,0 +1,485 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +import * as pda from "./pda"; +import * as T from "./types"; +import { + Commitment, + Connection, + GetAccountInfoConfig, + Keypair, + PublicKey, + sendAndConfirmTransaction, + SystemProgram, + Transaction, + TransactionInstruction, + TransactionSignature, +} from "@solana/web3.js"; +import {deserialize, serialize} from "borsh"; + + +let _programId: PublicKey; +let _connection: Connection; + +export const initializeClient = ( + programId: PublicKey, + connection: Connection +) => { + _programId = programId; + _connection = connection; +}; + +export enum ExpenseSplitterInstruction { +/** + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ + CreateGroup = 0, + +/** + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ + AddMember = 1, + +/** + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ + RemoveMember = 2, + +/** + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ + LogPayment = 3, +} + +export type CreateGroupArgs = { + feePayer: PublicKey; + groupName: string; + members: PublicKey[]; + initialBalances: bigint[]; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link TransactionInstruction} + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const createGroup = (args: CreateGroupArgs, remainingAccounts: Array = []): TransactionInstruction => { + const data = serialize( + { + struct: { + id: "u8", + group_name: "string", + members: { array: { type: { array: { type: "u8", len: 32 } } } }, + initial_balances: { array: { type: "i64" } }, + group_account_seed_group_name: "string", + }, + }, + { + id: ExpenseSplitterInstruction.CreateGroup, + group_name: args.groupName, + members: args.members.map(e => e.toBytes()), + initial_balances: args.initialBalances, + group_account_seed_group_name: args.groupAccountSeedGroupName, + } + ); + + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _programId); + + return new TransactionInstruction({ + data: Buffer.from(data), + keys: [ + {pubkey: args.feePayer, isSigner: true, isWritable: true}, + {pubkey: groupAccountPubkey, isSigner: false, isWritable: true}, + {pubkey: new PublicKey("11111111111111111111111111111111"), isSigner: false, isWritable: false}, + ...remainingAccounts.map(e => ({pubkey: e, isSigner: false, isWritable: false})), + ], + programId: _programId, + }); +}; + +/** + * ### Returns a {@link TransactionSignature} + * Creates a new group with initial members and balances. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * 2. `[]` system_program: {@link PublicKey} Auto-generated, for account initialization + * + * Data: + * - group_name: {@link string} The name of the group. + * - members: {@link PublicKey[]} Array of member public keys. + * - initial_balances: {@link BigInt[]} Array of initial balances for each member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const createGroupSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: Keypair, + } + }, + remainingAccounts: Array = [] +): Promise => { + const trx = new Transaction(); + + + trx.add(createGroup({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts)); + + return await sendAndConfirmTransaction( + _connection, + trx, + [ + args.signers.feePayer, + ] + ); +}; + +export type AddMemberArgs = { + feePayer: PublicKey; + newMember: PublicKey; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link TransactionInstruction} + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const addMember = (args: AddMemberArgs, remainingAccounts: Array = []): TransactionInstruction => { + const data = serialize( + { + struct: { + id: "u8", + new_member: { array: { type: "u8", len: 32 } }, + group_account_seed_group_name: "string", + }, + }, + { + id: ExpenseSplitterInstruction.AddMember, + new_member: args.newMember.toBytes(), + group_account_seed_group_name: args.groupAccountSeedGroupName, + } + ); + + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _programId); + + return new TransactionInstruction({ + data: Buffer.from(data), + keys: [ + {pubkey: args.feePayer, isSigner: true, isWritable: true}, + {pubkey: groupAccountPubkey, isSigner: false, isWritable: true}, + ...remainingAccounts.map(e => ({pubkey: e, isSigner: false, isWritable: false})), + ], + programId: _programId, + }); +}; + +/** + * ### Returns a {@link TransactionSignature} + * Adds a new member to the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - new_member: {@link PublicKey} Public key of the new member. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const addMemberSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: Keypair, + } + }, + remainingAccounts: Array = [] +): Promise => { + const trx = new Transaction(); + + + trx.add(addMember({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts)); + + return await sendAndConfirmTransaction( + _connection, + trx, + [ + args.signers.feePayer, + ] + ); +}; + +export type RemoveMemberArgs = { + feePayer: PublicKey; + member: PublicKey; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link TransactionInstruction} + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const removeMember = (args: RemoveMemberArgs, remainingAccounts: Array = []): TransactionInstruction => { + const data = serialize( + { + struct: { + id: "u8", + member: { array: { type: "u8", len: 32 } }, + group_account_seed_group_name: "string", + }, + }, + { + id: ExpenseSplitterInstruction.RemoveMember, + member: args.member.toBytes(), + group_account_seed_group_name: args.groupAccountSeedGroupName, + } + ); + + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _programId); + + return new TransactionInstruction({ + data: Buffer.from(data), + keys: [ + {pubkey: args.feePayer, isSigner: true, isWritable: true}, + {pubkey: groupAccountPubkey, isSigner: false, isWritable: true}, + ...remainingAccounts.map(e => ({pubkey: e, isSigner: false, isWritable: false})), + ], + programId: _programId, + }); +}; + +/** + * ### Returns a {@link TransactionSignature} + * Removes a member from the group. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - member: {@link PublicKey} Public key of the member to remove. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const removeMemberSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: Keypair, + } + }, + remainingAccounts: Array = [] +): Promise => { + const trx = new Transaction(); + + + trx.add(removeMember({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts)); + + return await sendAndConfirmTransaction( + _connection, + trx, + [ + args.signers.feePayer, + ] + ); +}; + +export type LogPaymentArgs = { + feePayer: PublicKey; + title: string; + participants: PublicKey[]; + amounts: bigint[]; + participationFactors: bigint[]; + groupAccountSeedGroupName: string; +}; + +/** + * ### Returns a {@link TransactionInstruction} + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const logPayment = (args: LogPaymentArgs, remainingAccounts: Array = []): TransactionInstruction => { + const data = serialize( + { + struct: { + id: "u8", + title: "string", + participants: { array: { type: { array: { type: "u8", len: 32 } } } }, + amounts: { array: { type: "u64" } }, + participation_factors: { array: { type: "u64" } }, + group_account_seed_group_name: "string", + }, + }, + { + id: ExpenseSplitterInstruction.LogPayment, + title: args.title, + participants: args.participants.map(e => e.toBytes()), + amounts: args.amounts, + participation_factors: args.participationFactors, + group_account_seed_group_name: args.groupAccountSeedGroupName, + } + ); + + const [groupAccountPubkey] = pda.deriveGroupAccountPDA({ + groupName: args.groupAccountSeedGroupName, + }, _programId); + + return new TransactionInstruction({ + data: Buffer.from(data), + keys: [ + {pubkey: args.feePayer, isSigner: true, isWritable: true}, + {pubkey: groupAccountPubkey, isSigner: false, isWritable: true}, + ...remainingAccounts.map(e => ({pubkey: e, isSigner: false, isWritable: false})), + ], + programId: _programId, + }); +}; + +/** + * ### Returns a {@link TransactionSignature} + * Logs a payment made by or for group members, updating balances accordingly. + * + * Accounts: + * 0. `[writable, signer]` fee_payer: {@link PublicKey} Auto-generated, default fee payer + * 1. `[writable]` group_account: {@link GroupAccount} + * + * Data: + * - title: {@link string} Title or description of the payment. + * - participants: {@link PublicKey[]} Array of participating member public keys. + * - amounts: {@link BigInt[]} Array of amounts each participant gave to the group. + * - participation_factors: {@link BigInt[]} Array of participation factors for each participant. + * - group_account_seed_group_name: {@link string} Auto-generated, from the input "group_account" for the its seed definition "GroupAccount", sets the seed named "group_name" + */ +export const logPaymentSendAndConfirm = async ( + args: Omit & { + signers: { + feePayer: Keypair, + } + }, + remainingAccounts: Array = [] +): Promise => { + const trx = new Transaction(); + + + trx.add(logPayment({ + ...args, + feePayer: args.signers.feePayer.publicKey, + }, remainingAccounts)); + + return await sendAndConfirmTransaction( + _connection, + trx, + [ + args.signers.feePayer, + ] + ); +}; + +// Getters + +export const getGroupAccount = async ( + publicKey: PublicKey, + commitmentOrConfig: Commitment | GetAccountInfoConfig | undefined = "processed" +): Promise => { + const buffer = await _connection.getAccountInfo(publicKey, commitmentOrConfig); + + if (!buffer) { + return undefined + } + + if (buffer.data.length <= 0) { + return undefined + } + + return T.decodeGroupAccount(deserialize(T.GroupAccountSchema, buffer.data) as Record); +} + + +// Websocket Events + diff --git a/examples/solana_native/expenses_splitting/program_client/lib/types.ts b/examples/solana_native/expenses_splitting/program_client/lib/types.ts new file mode 100644 index 0000000..5810a2b --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/lib/types.ts @@ -0,0 +1,31 @@ +// This file is auto-generated from the CIDL source. +// Editing this file directly is not recommended as it may be overwritten. + +import type {Schema} from 'borsh'; +import type {Decoded} from "./utils"; +import {PublicKey} from "@solana/web3.js"; +import { deserialize } from "borsh"; + +/// Represents a group account with members and their balances. +export interface GroupAccount { + groupName: string; + members: PublicKey[]; + balances: bigint[]; +} + +export const decodeGroupAccount = (decoded: Decoded): GroupAccount => ({ + groupName: decoded["group_name"] as string, + members: (decoded["members"] as Uint8Array[]).map(e => new PublicKey(e)), + balances: decoded["balances"] as bigint[], +}); + +export const GroupAccountSchema: Schema = { + struct: { + group_name: "string", + members: { array: { type: { array: { type: "u8", len: 32 } } } }, + balances: { array: { type: "i64" } }, + } +}; + + + diff --git a/examples/solana_native/expenses_splitting/program_client/lib/utils.ts b/examples/solana_native/expenses_splitting/program_client/lib/utils.ts new file mode 100644 index 0000000..3ae5cdd --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/lib/utils.ts @@ -0,0 +1 @@ +export type Decoded = Record; \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program_client/package.json b/examples/solana_native/expenses_splitting/program_client/package.json new file mode 100644 index 0000000..7bd94bd --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/package.json @@ -0,0 +1,23 @@ +{ + "name": "expense_splitter", + "version": "1.0.0", + "private": true, + "license": "MIT License", + "author": { + "name": "Developer Name", + "email": "contact@developerwebsite.com", + "url": "https://developerwebsite.com" + }, + "repository": { + "url": "https://github.com/developer/ExpenseSplitter" + }, + "description": "A contract for managing group expenses, allowing members to split costs fairly.", + "dependencies": { + "@solana/web3.js": "^1.78.3", + "borsh": "^1.0.0", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=14.0.0" + } +} \ No newline at end of file diff --git a/examples/solana_native/expenses_splitting/program_client/tsconfig.json b/examples/solana_native/expenses_splitting/program_client/tsconfig.json new file mode 100644 index 0000000..f31ec9f --- /dev/null +++ b/examples/solana_native/expenses_splitting/program_client/tsconfig.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "lib": [ + "ES2020" + ], + "module": "commonjs", + "target": "ES2020", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "outDir": "dist", + "baseUrl": "./", + "declaration": true + }, + "include": [ + "./lib/**/*" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file