├── .envrc ├── .eslintignore ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── programs-e2e.yml │ ├── programs-unit.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .mocharc.js ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── .yarn ├── plugins │ └── @yarnpkg │ │ ├── plugin-interactive-tools.cjs │ │ └── plugin-typescript.cjs ├── releases │ └── yarn-3.2.0.cjs └── sdks │ ├── eslint │ ├── bin │ │ └── eslint.js │ ├── lib │ │ └── api.js │ └── package.json │ ├── integrations.yml │ ├── prettier │ ├── index.js │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ ├── tsserverlibrary.js │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── ci.nix ├── flake.lock ├── flake.nix ├── images └── cashio.png ├── package.json ├── programs ├── bankman │ ├── Cargo.toml │ ├── README.md │ ├── Xargo.toml │ └── src │ │ ├── account_validators.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── mod.rs │ │ └── withdraw_author_fee.rs │ │ ├── lib.rs │ │ └── state.rs └── brrr │ ├── Cargo.toml │ ├── README.md │ ├── Xargo.toml │ ├── converter │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs │ └── src │ ├── actions │ ├── burn_cash.rs │ ├── mod.rs │ └── print_cash.rs │ ├── addresses.rs │ ├── events.rs │ ├── lib.rs │ └── saber.rs ├── scripts ├── download-programs.sh ├── generate-idl-types.sh └── parse-idls.sh ├── shell.nix ├── src ├── cashio.ts ├── constants.ts ├── index.ts ├── parsers.ts ├── pda.ts └── programs │ ├── bankman.ts │ ├── brrr.ts │ └── index.ts ├── tests ├── cashio.ts ├── fixture-key.json ├── quarryUtils.ts ├── sunnyUtils.ts └── workspace.ts ├── tsconfig.build.json ├── tsconfig.esm.json ├── tsconfig.json └── yarn.lock /.envrc: -------------------------------------------------------------------------------- 1 | watch_file flake.nix 2 | watch_file flake.lock 3 | mkdir -p .direnv 4 | dotenv 5 | eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile")" 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | require("@rushstack/eslint-patch/modern-module-resolution"); 2 | 3 | module.exports = { 4 | extends: ["@saberhq/eslint-config"], 5 | parserOptions: { 6 | project: "tsconfig.json", 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "cargo" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/programs-e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | SOLANA_VERSION: "1.9.9" 12 | RUST_TOOLCHAIN: nightly-2021-12-23 13 | 14 | jobs: 15 | sdk: 16 | runs-on: ubuntu-latest 17 | name: Build the SDK 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - uses: cachix/install-nix-action@v17 22 | - name: Setup Cachix 23 | uses: cachix/cachix-action@v10 24 | with: 25 | name: cash 26 | extraPullNames: quarry, saber 27 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 28 | - name: Parse IDLs 29 | run: nix shell .#ci --command ./scripts/parse-idls.sh 30 | 31 | - name: Setup Node 32 | uses: actions/setup-node@v3 33 | 34 | - name: Get yarn cache directory path 35 | id: yarn-cache-dir-path 36 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 37 | - name: Yarn Cache 38 | uses: actions/cache@v3.0.1 39 | with: 40 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 41 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 42 | restore-keys: | 43 | ${{ runner.os }}-modules- 44 | 45 | - name: Install Yarn dependencies 46 | run: yarn install 47 | - run: ./scripts/generate-idl-types.sh 48 | - run: yarn build 49 | - run: yarn typecheck 50 | - run: yarn lint 51 | - run: yarn dlx @yarnpkg/doctor 52 | 53 | integration-tests: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v3 57 | 58 | # Install Rust and Anchor 59 | - name: Install Rust nightly 60 | uses: actions-rs/toolchain@v1 61 | with: 62 | override: true 63 | profile: minimal 64 | toolchain: ${{ env.RUST_TOOLCHAIN }} 65 | - uses: Swatinem/rust-cache@v1 66 | - name: Install Linux dependencies 67 | run: | 68 | sudo apt-get update 69 | sudo apt-get install -y pkg-config build-essential libudev-dev 70 | 71 | # Install Nix 72 | - uses: cachix/install-nix-action@v17 73 | - name: Setup Cachix 74 | uses: cachix/cachix-action@v10 75 | with: 76 | name: cash 77 | extraPullNames: quarry, saber 78 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 79 | 80 | # Install Solana 81 | - name: Cache Solana binaries 82 | id: solana-cache 83 | uses: actions/cache@v3.0.1 84 | with: 85 | path: | 86 | ~/.cache/solana 87 | ~/.local/share/solana/install 88 | key: ${{ runner.os }}-${{ env.SOLANA_VERSION }} 89 | - name: Install Solana 90 | if: steps.solana-cache.outputs.cache-hit != 'true' 91 | run: | 92 | nix shell .#ci --command solana-install init ${{ env.SOLANA_VERSION }} 93 | - name: Setup Solana Path 94 | run: | 95 | echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH 96 | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" 97 | solana --version 98 | 99 | # Run build 100 | - name: Build program 101 | run: nix shell .#ci --command anchor build 102 | 103 | - name: Get yarn cache directory path 104 | id: yarn-cache-dir-path 105 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 106 | - name: Yarn Cache 107 | uses: actions/cache@v3.0.1 108 | with: 109 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 110 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 111 | restore-keys: | 112 | ${{ runner.os }}-modules- 113 | 114 | - name: Setup Node 115 | uses: actions/setup-node@v3 116 | - run: yarn install 117 | - name: Generate IDL types 118 | run: nix shell .#ci --command yarn idl:generate:nolint 119 | - run: yarn build 120 | - run: ./scripts/download-programs.sh 121 | - name: Run e2e tests 122 | run: nix shell .#ci --command yarn test:e2e 123 | -------------------------------------------------------------------------------- /.github/workflows/programs-unit.yml: -------------------------------------------------------------------------------- 1 | name: Unit 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - .github/workflows/programs-unit.yml 8 | - programs/** 9 | - Cargo.toml 10 | - Cargo.lock 11 | pull_request: 12 | branches: [master] 13 | paths: 14 | - .github/workflows/programs-unit.yml 15 | - programs/** 16 | - Cargo.toml 17 | - Cargo.lock 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | RUST_TOOLCHAIN: nightly-2021-12-23 22 | 23 | jobs: 24 | lint: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Install Rust nightly 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | override: true 32 | profile: minimal 33 | toolchain: ${{ env.RUST_TOOLCHAIN }} 34 | components: rustfmt, clippy 35 | - uses: Swatinem/rust-cache@v1 36 | - name: Run fmt 37 | run: cargo fmt -- --check 38 | - name: Run clippy 39 | run: cargo clippy --all-targets -- --deny=warnings 40 | 41 | unit-tests: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | - name: Install Rust nightly 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | override: true 49 | profile: minimal 50 | toolchain: ${{ env.RUST_TOOLCHAIN }} 51 | components: rustfmt, clippy 52 | - uses: Swatinem/rust-cache@v1 53 | - name: Run unit tests 54 | run: cargo test --lib 55 | 56 | doc: 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v3 60 | - name: Install Rust nightly 61 | uses: actions-rs/toolchain@v1 62 | with: 63 | override: true 64 | profile: minimal 65 | toolchain: ${{ env.RUST_TOOLCHAIN }} 66 | components: rustfmt, clippy 67 | - uses: Swatinem/rust-cache@v1 68 | - run: cargo doc 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | SOLANA_VERSION: "1.9.9" 12 | RUST_TOOLCHAIN: nightly-2021-12-23 13 | NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 14 | 15 | jobs: 16 | release-sdk: 17 | runs-on: ubuntu-latest 18 | name: Release SDK on NPM 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - uses: cachix/install-nix-action@v17 23 | - name: Setup Cachix 24 | uses: cachix/cachix-action@v10 25 | with: 26 | name: cash 27 | extraPullNames: quarry, saber 28 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 29 | - name: Setup Node 30 | uses: actions/setup-node@v3 31 | 32 | - name: Get yarn cache directory path 33 | id: yarn-cache-dir-path 34 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 35 | - name: Yarn Cache 36 | uses: actions/cache@v3.0.1 37 | with: 38 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 39 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 40 | restore-keys: | 41 | ${{ runner.os }}-modules- 42 | 43 | - name: Install Yarn dependencies 44 | run: yarn install 45 | - name: Parse IDLs 46 | run: nix shell .#ci --command yarn idl:generate 47 | - run: yarn build 48 | - run: | 49 | echo 'npmAuthToken: "${NPM_PUBLISH_TOKEN}"' >> .yarnrc.yml 50 | - name: Publish 51 | run: yarn npm publish 52 | 53 | release-crate: 54 | runs-on: ubuntu-latest 55 | name: Release crate on crates.io 56 | steps: 57 | - uses: actions/checkout@v3 58 | - name: Install Rust nightly 59 | uses: actions-rs/toolchain@v1 60 | with: 61 | override: true 62 | profile: minimal 63 | toolchain: ${{ env.RUST_TOOLCHAIN }} 64 | - uses: Swatinem/rust-cache@v1 65 | 66 | - uses: cachix/install-nix-action@v17 67 | - name: Setup Cachix 68 | uses: cachix/cachix-action@v10 69 | with: 70 | name: cash 71 | extraPullNames: quarry, saber 72 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 73 | 74 | - name: Publish crates 75 | run: nix shell .#ci --command cargo ws publish --from-git --yes --skip-published --token ${{ secrets.CARGO_PUBLISH_TOKEN }} 76 | 77 | release-binaries: 78 | runs-on: ubuntu-latest 79 | name: Release verifiable binaries 80 | steps: 81 | - uses: actions/checkout@v3 82 | - uses: cachix/install-nix-action@v17 83 | - name: Setup Cachix 84 | uses: cachix/cachix-action@v10 85 | with: 86 | name: cash 87 | extraPullNames: quarry, saber 88 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 89 | 90 | - name: Build programs 91 | run: nix shell .#ci --command anchor build -v 92 | - name: Release 93 | uses: softprops/action-gh-release@v1 94 | with: 95 | files: | 96 | target/deploy/* 97 | target/idl/* 98 | target/verifiable/* 99 | 100 | site: 101 | runs-on: ubuntu-latest 102 | steps: 103 | - name: Checkout 104 | uses: actions/checkout@v3 105 | 106 | - uses: cachix/install-nix-action@v17 107 | - name: Setup Cachix 108 | uses: cachix/cachix-action@v10 109 | with: 110 | name: cash 111 | extraPullNames: quarry, saber 112 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 113 | 114 | - name: Get yarn cache directory path 115 | id: yarn-cache-dir-path 116 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 117 | - name: Yarn Cache 118 | uses: actions/cache@v3.0.1 119 | with: 120 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 121 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 122 | restore-keys: | 123 | ${{ runner.os }}-modules- 124 | 125 | - name: Install Yarn dependencies 126 | run: yarn install 127 | - name: Parse IDLs 128 | run: nix shell .#ci --command yarn idl:generate 129 | - run: yarn docs:generate 130 | - run: cp -R images/ site/ 131 | 132 | - name: Deploy 🚀 133 | uses: JamesIves/github-pages-deploy-action@v4.3.0 134 | with: 135 | branch: gh-pages 136 | folder: site 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .anchor/ 2 | node_modules/ 3 | artifacts/ 4 | dist/ 5 | target/ 6 | yarn-error.log 7 | 8 | .yarn/* 9 | !.yarn/patches 10 | !.yarn/releases 11 | !.yarn/plugins 12 | !.yarn/sdks 13 | !.yarn/versions 14 | .pnp.* 15 | 16 | artifacts/ 17 | src/idls/ 18 | 19 | .eslintcache 20 | site/ 21 | Captain.toml 22 | deployments/ 23 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | require("./.pnp.cjs").setup(); 2 | 3 | module.exports = { 4 | timeout: 30_000, 5 | require: [require.resolve("ts-node/register")], 6 | }; 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .yarn/ 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.yarn": true, 4 | "**/.pnp.*": true 5 | }, 6 | "eslint.nodePath": ".yarn/sdks", 7 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 8 | "typescript.enablePromptUseWorkspaceTsdk": true, 9 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js" 10 | } 11 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-typescript.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-typescript", 5 | factory: function (require) { 6 | var plugin=(()=>{var Ft=Object.create,H=Object.defineProperty,Bt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,zt=Object.getOwnPropertyDescriptors,Gt=Object.getOwnPropertyNames,Q=Object.getOwnPropertySymbols,$t=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,De=Object.prototype.propertyIsEnumerable;var Re=(e,t,r)=>t in e?H(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,u=(e,t)=>{for(var r in t||(t={}))ne.call(t,r)&&Re(e,r,t[r]);if(Q)for(var r of Q(t))De.call(t,r)&&Re(e,r,t[r]);return e},g=(e,t)=>Bt(e,zt(t)),Lt=e=>H(e,"__esModule",{value:!0});var R=(e,t)=>{var r={};for(var s in e)ne.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(e!=null&&Q)for(var s of Q(e))t.indexOf(s)<0&&De.call(e,s)&&(r[s]=e[s]);return r};var I=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Vt=(e,t)=>{for(var r in t)H(e,r,{get:t[r],enumerable:!0})},Qt=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Gt(t))!ne.call(e,s)&&s!=="default"&&H(e,s,{get:()=>t[s],enumerable:!(r=Kt(t,s))||r.enumerable});return e},C=e=>Qt(Lt(H(e!=null?Ft($t(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var xe=I(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});function _(e){let t=[...e.caches],r=t.shift();return r===void 0?ve():{get(s,n,a={miss:()=>Promise.resolve()}){return r.get(s,n,a).catch(()=>_({caches:t}).get(s,n,a))},set(s,n){return r.set(s,n).catch(()=>_({caches:t}).set(s,n))},delete(s){return r.delete(s).catch(()=>_({caches:t}).delete(s))},clear(){return r.clear().catch(()=>_({caches:t}).clear())}}}function ve(){return{get(e,t,r={miss:()=>Promise.resolve()}){return t().then(n=>Promise.all([n,r.miss(n)])).then(([n])=>n)},set(e,t){return Promise.resolve(t)},delete(e){return Promise.resolve()},clear(){return Promise.resolve()}}}J.createFallbackableCache=_;J.createNullCache=ve});var Ee=I(($s,qe)=>{qe.exports=xe()});var Te=I(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});function Jt(e={serializable:!0}){let t={};return{get(r,s,n={miss:()=>Promise.resolve()}){let a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);let o=s(),d=n&&n.miss||(()=>Promise.resolve());return o.then(y=>d(y)).then(()=>o)},set(r,s){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete t[JSON.stringify(r)],Promise.resolve()},clear(){return t={},Promise.resolve()}}}ae.createInMemoryCache=Jt});var we=I((Vs,Me)=>{Me.exports=Te()});var Ce=I(M=>{"use strict";Object.defineProperty(M,"__esModule",{value:!0});function Xt(e,t,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers(){return e===oe.WithinHeaders?s:{}},queryParameters(){return e===oe.WithinQueryParameters?s:{}}}}function Yt(e){let t=0,r=()=>(t++,new Promise(s=>{setTimeout(()=>{s(e(r))},Math.min(100*t,1e3))}));return e(r)}function ke(e,t=(r,s)=>Promise.resolve()){return Object.assign(e,{wait(r){return ke(e.then(s=>Promise.all([t(s,r),s])).then(s=>s[1]))}})}function Zt(e){let t=e.length-1;for(t;t>0;t--){let r=Math.floor(Math.random()*(t+1)),s=e[t];e[t]=e[r],e[r]=s}return e}function er(e,t){return Object.keys(t!==void 0?t:{}).forEach(r=>{e[r]=t[r](e)}),e}function tr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}var rr="4.2.0",sr=e=>()=>e.transporter.requester.destroy(),oe={WithinQueryParameters:0,WithinHeaders:1};M.AuthMode=oe;M.addMethods=er;M.createAuth=Xt;M.createRetryablePromise=Yt;M.createWaitablePromise=ke;M.destroy=sr;M.encode=tr;M.shuffle=Zt;M.version=rr});var F=I((Js,Ue)=>{Ue.exports=Ce()});var Ne=I(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});var nr={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};ie.MethodEnum=nr});var B=I((Ys,We)=>{We.exports=Ne()});var Ze=I(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});var He=B();function ce(e,t){let r=e||{},s=r.data||{};return Object.keys(r).forEach(n=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(n)===-1&&(s[n]=r[n])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var X={Read:1,Write:2,Any:3},U={Up:1,Down:2,Timeouted:3},_e=2*60*1e3;function ue(e,t=U.Up){return g(u({},e),{status:t,lastUpdate:Date.now()})}function Fe(e){return e.status===U.Up||Date.now()-e.lastUpdate>_e}function Be(e){return e.status===U.Timeouted&&Date.now()-e.lastUpdate<=_e}function le(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||X.Any}}function ar(e,t){return Promise.all(t.map(r=>e.get(r,()=>Promise.resolve(ue(r))))).then(r=>{let s=r.filter(d=>Fe(d)),n=r.filter(d=>Be(d)),a=[...s,...n],o=a.length>0?a.map(d=>le(d)):t;return{getTimeout(d,y){return(n.length===0&&d===0?1:n.length+3+d)*y},statelessHosts:o}})}var or=({isTimedOut:e,status:t})=>!e&&~~t==0,ir=e=>{let t=e.status;return e.isTimedOut||or(e)||~~(t/100)!=2&&~~(t/100)!=4},cr=({status:e})=>~~(e/100)==2,ur=(e,t)=>ir(e)?t.onRetry(e):cr(e)?t.onSucess(e):t.onFail(e);function Qe(e,t,r,s){let n=[],a=$e(r,s),o=Le(e,s),d=r.method,y=r.method!==He.MethodEnum.Get?{}:u(u({},r.data),s.data),b=u(u(u({"x-algolia-agent":e.userAgent.value},e.queryParameters),y),s.queryParameters),f=0,p=(h,S)=>{let O=h.pop();if(O===void 0)throw Ve(de(n));let P={data:a,headers:o,method:d,url:Ge(O,r.path,b),connectTimeout:S(f,e.timeouts.connect),responseTimeout:S(f,s.timeout)},x=j=>{let T={request:P,response:j,host:O,triesLeft:h.length};return n.push(T),T},v={onSucess:j=>Ke(j),onRetry(j){let T=x(j);return j.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",pe(T)),e.hostsCache.set(O,ue(O,j.isTimedOut?U.Timeouted:U.Down))]).then(()=>p(h,S))},onFail(j){throw x(j),ze(j,de(n))}};return e.requester.send(P).then(j=>ur(j,v))};return ar(e.hostsCache,t).then(h=>p([...h.statelessHosts].reverse(),h.getTimeout))}function lr(e){let{hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,hosts:y,queryParameters:b,headers:f}=e,p={hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,headers:f,queryParameters:b,hosts:y.map(h=>le(h)),read(h,S){let O=ce(S,p.timeouts.read),P=()=>Qe(p,p.hosts.filter(j=>(j.accept&X.Read)!=0),h,O);if((O.cacheable!==void 0?O.cacheable:h.cacheable)!==!0)return P();let v={request:h,mappedRequestOptions:O,transporter:{queryParameters:p.queryParameters,headers:p.headers}};return p.responsesCache.get(v,()=>p.requestsCache.get(v,()=>p.requestsCache.set(v,P()).then(j=>Promise.all([p.requestsCache.delete(v),j]),j=>Promise.all([p.requestsCache.delete(v),Promise.reject(j)])).then(([j,T])=>T)),{miss:j=>p.responsesCache.set(v,j)})},write(h,S){return Qe(p,p.hosts.filter(O=>(O.accept&X.Write)!=0),h,ce(S,p.timeouts.write))}};return p}function dr(e){let t={value:`Algolia for JavaScript (${e})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return t.value.indexOf(s)===-1&&(t.value=`${t.value}${s}`),t}};return t}function Ke(e){try{return JSON.parse(e.content)}catch(t){throw Je(t.message,e)}}function ze({content:e,status:t},r){let s=e;try{s=JSON.parse(e).message}catch(n){}return Xe(s,t,r)}function pr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}function Ge(e,t,r){let s=Ye(r),n=`${e.protocol}://${e.url}/${t.charAt(0)==="/"?t.substr(1):t}`;return s.length&&(n+=`?${s}`),n}function Ye(e){let t=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(e).map(r=>pr("%s=%s",r,t(e[r])?JSON.stringify(e[r]):e[r])).join("&")}function $e(e,t){if(e.method===He.MethodEnum.Get||e.data===void 0&&t.data===void 0)return;let r=Array.isArray(e.data)?e.data:u(u({},e.data),t.data);return JSON.stringify(r)}function Le(e,t){let r=u(u({},e.headers),t.headers),s={};return Object.keys(r).forEach(n=>{let a=r[n];s[n.toLowerCase()]=a}),s}function de(e){return e.map(t=>pe(t))}function pe(e){let t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return g(u({},e),{request:g(u({},e.request),{headers:u(u({},e.request.headers),t)})})}function Xe(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}function Je(e,t){return{name:"DeserializationError",message:e,response:t}}function Ve(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}A.CallEnum=X;A.HostStatusEnum=U;A.createApiError=Xe;A.createDeserializationError=Je;A.createMappedRequestOptions=ce;A.createRetryError=Ve;A.createStatefulHost=ue;A.createStatelessHost=le;A.createTransporter=lr;A.createUserAgent=dr;A.deserializeFailure=ze;A.deserializeSuccess=Ke;A.isStatefulHostTimeouted=Be;A.isStatefulHostUp=Fe;A.serializeData=$e;A.serializeHeaders=Le;A.serializeQueryParameters=Ye;A.serializeUrl=Ge;A.stackFrameWithoutCredentials=pe;A.stackTraceWithoutCredentials=de});var K=I((en,et)=>{et.exports=Ze()});var tt=I(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});var N=F(),mr=K(),z=B(),hr=e=>{let t=e.region||"us",r=N.createAuth(N.AuthMode.WithinHeaders,e.appId,e.apiKey),s=mr.createTransporter(g(u({hosts:[{url:`analytics.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n=e.appId;return N.addMethods({appId:n,transporter:s},e.methods)},yr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:"2/abtests",data:t},r),gr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Delete,path:N.encode("2/abtests/%s",t)},r),fr=e=>(t,r)=>e.transporter.read({method:z.MethodEnum.Get,path:N.encode("2/abtests/%s",t)},r),br=e=>t=>e.transporter.read({method:z.MethodEnum.Get,path:"2/abtests"},t),Pr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:N.encode("2/abtests/%s/stop",t)},r);w.addABTest=yr;w.createAnalyticsClient=hr;w.deleteABTest=gr;w.getABTest=fr;w.getABTests=br;w.stopABTest=Pr});var st=I((rn,rt)=>{rt.exports=tt()});var at=I(G=>{"use strict";Object.defineProperty(G,"__esModule",{value:!0});var me=F(),jr=K(),nt=B(),Or=e=>{let t=e.region||"us",r=me.createAuth(me.AuthMode.WithinHeaders,e.appId,e.apiKey),s=jr.createTransporter(g(u({hosts:[{url:`recommendation.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)}));return me.addMethods({appId:e.appId,transporter:s},e.methods)},Ir=e=>t=>e.transporter.read({method:nt.MethodEnum.Get,path:"1/strategies/personalization"},t),Ar=e=>(t,r)=>e.transporter.write({method:nt.MethodEnum.Post,path:"1/strategies/personalization",data:t},r);G.createRecommendationClient=Or;G.getPersonalizationStrategy=Ir;G.setPersonalizationStrategy=Ar});var it=I((nn,ot)=>{ot.exports=at()});var jt=I(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});var l=F(),q=K(),m=B(),Sr=require("crypto");function Y(e){let t=r=>e.request(r).then(s=>{if(e.batch!==void 0&&e.batch(s.hits),!e.shouldStop(s))return s.cursor?t({cursor:s.cursor}):t({page:(r.page||0)+1})});return t({})}var Dr=e=>{let t=e.appId,r=l.createAuth(e.authMode!==void 0?e.authMode:l.AuthMode.WithinHeaders,t,e.apiKey),s=q.createTransporter(g(u({hosts:[{url:`${t}-dsn.algolia.net`,accept:q.CallEnum.Read},{url:`${t}.algolia.net`,accept:q.CallEnum.Write}].concat(l.shuffle([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}]))},e),{headers:u(g(u({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n={transporter:s,appId:t,addAlgoliaAgent(a,o){s.userAgent.add({segment:a,version:o})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return l.addMethods(n,e.methods)};function ct(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ut(){return{name:"ObjectNotFoundError",message:"Object not found."}}function lt(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Rr=e=>(t,r)=>{let d=r||{},{queryParameters:s}=d,n=R(d,["queryParameters"]),a=u({acl:t},s!==void 0?{queryParameters:s}:{}),o=(y,b)=>l.createRetryablePromise(f=>$(e)(y.key,b).catch(p=>{if(p.status!==404)throw p;return f()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/keys",data:a},n),o)},vr=e=>(t,r,s)=>{let n=q.createMappedRequestOptions(s);return n.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},n)},xr=e=>(t,r,s)=>e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:r}},s),Z=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"copy",destination:r}},s),n)},qr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Rules]})),Er=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Settings]})),Tr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Synonyms]})),Mr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).then(o).catch(d=>{if(d.status!==404)throw d}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/keys/%s",t)},r),s)},wr=()=>(e,t)=>{let r=q.serializeQueryParameters(t),s=Sr.createHmac("sha256",e).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},$=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/keys/%s",t)},r),kr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/logs"},t),Cr=()=>e=>{let t=Buffer.from(e,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=t.match(r);if(s===null)throw lt();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Ur=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/top"},t),Nr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/clusters/mapping/%s",t)},r),Wr=e=>t=>{let n=t||{},{retrieveMappings:r}=n,s=R(n,["retrieveMappings"]);return r===!0&&(s.getClusters=!0),e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},L=e=>(t,r={})=>{let s={transporter:e.transporter,appId:e.appId,indexName:t};return l.addMethods(s,r.methods)},Hr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/keys"},t),_r=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters"},t),Fr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/indexes"},t),Br=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping"},t),Kr=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"move",destination:r}},s),n)},zr=e=>(t,r)=>{let s=(n,a)=>Promise.all(Object.keys(n.taskID).map(o=>L(e)(o,{methods:{waitTask:D}}).waitTask(n.taskID[o],a)));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:t}},r),s)},Gr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:t}},r),$r=e=>(t,r)=>{let s=t.map(n=>g(u({},n),{params:q.serializeQueryParameters(n.params||{})}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Lr=e=>(t,r)=>Promise.all(t.map(s=>{let d=s.params,{facetName:n,facetQuery:a}=d,o=R(d,["facetName","facetQuery"]);return L(e)(s.indexName,{methods:{searchForFacetValues:dt}}).searchForFacetValues(n,a,u(u({},r),o))})),Vr=e=>(t,r)=>{let s=q.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Qr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).catch(d=>{if(d.status!==404)throw d;return o()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/keys/%s/restore",t)},r),s)},Jr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:t}},r),Xr=e=>(t,r)=>{let s=Object.assign({},r),f=r||{},{queryParameters:n}=f,a=R(f,["queryParameters"]),o=n?{queryParameters:n}:{},d=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],y=p=>Object.keys(s).filter(h=>d.indexOf(h)!==-1).every(h=>p[h]===s[h]),b=(p,h)=>l.createRetryablePromise(S=>$(e)(t,h).then(O=>y(O)?Promise.resolve():S()));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/keys/%s",t),data:o},a),b)},pt=e=>(t,r)=>{let s=(n,a)=>D(e)(n.taskID,a);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/batch",e.indexName),data:{requests:t}},r),s)},Yr=e=>t=>Y(g(u({},t),{shouldStop:r=>r.cursor===void 0,request:r=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/browse",e.indexName),data:r},t)})),Zr=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},es=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},te=e=>(t,r,s)=>{let y=s||{},{batchSize:n}=y,a=R(y,["batchSize"]),o={taskIDs:[],objectIDs:[]},d=(b=0)=>{let f=[],p;for(p=b;p({action:r,body:h})),a).then(h=>(o.objectIDs=o.objectIDs.concat(h.objectIDs),o.taskIDs.push(h.taskID),p++,d(p)))};return l.createWaitablePromise(d(),(b,f)=>Promise.all(b.taskIDs.map(p=>D(e)(p,f))))},ts=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/clear",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),rs=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ss=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ns=e=>(t,r)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/deleteByQuery",e.indexName),data:t},r),(s,n)=>D(e)(s.taskID,n)),as=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),os=e=>(t,r)=>l.createWaitablePromise(yt(e)([t],r).then(s=>({taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),yt=e=>(t,r)=>{let s=t.map(n=>({objectID:n}));return te(e)(s,k.DeleteObject,r)},is=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},cs=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},us=e=>t=>gt(e)(t).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),ls=e=>(t,r)=>{let y=r||{},{query:s,paginate:n}=y,a=R(y,["query","paginate"]),o=0,d=()=>ft(e)(s||"",g(u({},a),{page:o})).then(b=>{for(let[f,p]of Object.entries(b.hits))if(t(p))return{object:p,position:parseInt(f,10),page:o};if(o++,n===!1||o>=b.nbPages)throw ut();return d()});return d()},ds=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/%s",e.indexName,t)},r),ps=()=>(e,t)=>{for(let[r,s]of Object.entries(e.hits))if(s.objectID===t)return parseInt(r,10);return-1},ms=e=>(t,r)=>{let o=r||{},{attributesToRetrieve:s}=o,n=R(o,["attributesToRetrieve"]),a=t.map(d=>u({indexName:e.indexName,objectID:d},s?{attributesToRetrieve:s}:{}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:a}},n)},hs=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},r),gt=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),ys=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},r),bt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/task/%s",e.indexName,t.toString())},r),gs=e=>(t,r)=>l.createWaitablePromise(Pt(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),Pt=e=>(t,r)=>{let o=r||{},{createIfNotExists:s}=o,n=R(o,["createIfNotExists"]),a=s?k.PartialUpdateObject:k.PartialUpdateObjectNoCreate;return te(e)(t,a,n)},fs=e=>(t,r)=>{let O=r||{},{safe:s,autoGenerateObjectIDIfNotExist:n,batchSize:a}=O,o=R(O,["safe","autoGenerateObjectIDIfNotExist","batchSize"]),d=(P,x,v,j)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",P),data:{operation:v,destination:x}},j),(T,V)=>D(e)(T.taskID,V)),y=Math.random().toString(36).substring(7),b=`${e.indexName}_tmp_${y}`,f=he({appId:e.appId,transporter:e.transporter,indexName:b}),p=[],h=d(e.indexName,b,"copy",g(u({},o),{scope:["settings","synonyms","rules"]}));p.push(h);let S=(s?h.wait(o):h).then(()=>{let P=f(t,g(u({},o),{autoGenerateObjectIDIfNotExist:n,batchSize:a}));return p.push(P),s?P.wait(o):P}).then(()=>{let P=d(b,e.indexName,"move",o);return p.push(P),s?P.wait(o):P}).then(()=>Promise.all(p)).then(([P,x,v])=>({objectIDs:x.objectIDs,taskIDs:[P.taskID,...x.taskIDs,v.taskID]}));return l.createWaitablePromise(S,(P,x)=>Promise.all(p.map(v=>v.wait(x))))},bs=e=>(t,r)=>ye(e)(t,g(u({},r),{clearExistingRules:!0})),Ps=e=>(t,r)=>ge(e)(t,g(u({},r),{replaceExistingSynonyms:!0})),js=e=>(t,r)=>l.createWaitablePromise(he(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),he=e=>(t,r)=>{let o=r||{},{autoGenerateObjectIDIfNotExist:s}=o,n=R(o,["autoGenerateObjectIDIfNotExist"]),a=s?k.AddObject:k.UpdateObject;if(a===k.UpdateObject){for(let d of t)if(d.objectID===void 0)return l.createWaitablePromise(Promise.reject(ct()))}return te(e)(t,a,n)},Os=e=>(t,r)=>ye(e)([t],r),ye=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,clearExistingRules:n}=d,a=R(d,["forwardToReplicas","clearExistingRules"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.clearExistingRules=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},Is=e=>(t,r)=>ge(e)([t],r),ge=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,replaceExistingSynonyms:n}=d,a=R(d,["forwardToReplicas","replaceExistingSynonyms"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.replaceExistingSynonyms=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},ft=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),dt=e=>(t,r,s)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},s),mt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/search",e.indexName),data:{query:t}},r),ht=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},r),As=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/indexes/%s/settings",e.indexName),data:t},a),(d,y)=>D(e)(d.taskID,y))},D=e=>(t,r)=>l.createRetryablePromise(s=>bt(e)(t,r).then(n=>n.status!=="published"?s():void 0)),Ss={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},k={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},ee={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Ds={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Rs={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};i.ApiKeyACLEnum=Ss;i.BatchActionEnum=k;i.ScopeEnum=ee;i.StrategyEnum=Ds;i.SynonymEnum=Rs;i.addApiKey=Rr;i.assignUserID=vr;i.assignUserIDs=xr;i.batch=pt;i.browseObjects=Yr;i.browseRules=Zr;i.browseSynonyms=es;i.chunkedBatch=te;i.clearObjects=ts;i.clearRules=rs;i.clearSynonyms=ss;i.copyIndex=Z;i.copyRules=qr;i.copySettings=Er;i.copySynonyms=Tr;i.createBrowsablePromise=Y;i.createMissingObjectIDError=ct;i.createObjectNotFoundError=ut;i.createSearchClient=Dr;i.createValidUntilNotFoundError=lt;i.deleteApiKey=Mr;i.deleteBy=ns;i.deleteIndex=as;i.deleteObject=os;i.deleteObjects=yt;i.deleteRule=is;i.deleteSynonym=cs;i.exists=us;i.findObject=ls;i.generateSecuredApiKey=wr;i.getApiKey=$;i.getLogs=kr;i.getObject=ds;i.getObjectPosition=ps;i.getObjects=ms;i.getRule=hs;i.getSecuredApiKeyRemainingValidity=Cr;i.getSettings=gt;i.getSynonym=ys;i.getTask=bt;i.getTopUserIDs=Ur;i.getUserID=Nr;i.hasPendingMappings=Wr;i.initIndex=L;i.listApiKeys=Hr;i.listClusters=_r;i.listIndices=Fr;i.listUserIDs=Br;i.moveIndex=Kr;i.multipleBatch=zr;i.multipleGetObjects=Gr;i.multipleQueries=$r;i.multipleSearchForFacetValues=Lr;i.partialUpdateObject=gs;i.partialUpdateObjects=Pt;i.removeUserID=Vr;i.replaceAllObjects=fs;i.replaceAllRules=bs;i.replaceAllSynonyms=Ps;i.restoreApiKey=Qr;i.saveObject=js;i.saveObjects=he;i.saveRule=Os;i.saveRules=ye;i.saveSynonym=Is;i.saveSynonyms=ge;i.search=ft;i.searchForFacetValues=dt;i.searchRules=mt;i.searchSynonyms=ht;i.searchUserIDs=Jr;i.setSettings=As;i.updateApiKey=Xr;i.waitTask=D});var It=I((on,Ot)=>{Ot.exports=jt()});var At=I(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});function vs(){return{debug(e,t){return Promise.resolve()},info(e,t){return Promise.resolve()},error(e,t){return Promise.resolve()}}}var xs={Debug:1,Info:2,Error:3};re.LogLevelEnum=xs;re.createNullLogger=vs});var Dt=I((un,St)=>{St.exports=At()});var xt=I(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});var Rt=require("http"),vt=require("https"),qs=require("url");function Es(){let e={keepAlive:!0},t=new Rt.Agent(e),r=new vt.Agent(e);return{send(s){return new Promise(n=>{let a=qs.parse(s.url),o=a.query===null?a.pathname:`${a.pathname}?${a.query}`,d=u({agent:a.protocol==="https:"?r:t,hostname:a.hostname,path:o,method:s.method,headers:s.headers},a.port!==void 0?{port:a.port||""}:{}),y=(a.protocol==="https:"?vt:Rt).request(d,h=>{let S="";h.on("data",O=>S+=O),h.on("end",()=>{clearTimeout(f),clearTimeout(p),n({status:h.statusCode||0,content:S,isTimedOut:!1})})}),b=(h,S)=>setTimeout(()=>{y.abort(),n({status:0,content:S,isTimedOut:!0})},h*1e3),f=b(s.connectTimeout,"Connection timeout"),p;y.on("error",h=>{clearTimeout(f),clearTimeout(p),n({status:0,content:h.message,isTimedOut:!1})}),y.once("response",()=>{clearTimeout(f),p=b(s.responseTimeout,"Socket timeout")}),s.data!==void 0&&y.write(s.data),y.end()})},destroy(){return t.destroy(),r.destroy(),Promise.resolve()}}}fe.createNodeHttpRequester=Es});var Et=I((dn,qt)=>{qt.exports=xt()});var kt=I((pn,Tt)=>{"use strict";var Mt=Ee(),Ts=we(),W=st(),be=F(),Pe=it(),c=It(),Ms=Dt(),ws=Et(),ks=K();function wt(e,t,r){let s={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:ws.createNodeHttpRequester(),logger:Ms.createNullLogger(),responsesCache:Mt.createNullCache(),requestsCache:Mt.createNullCache(),hostsCache:Ts.createInMemoryCache(),userAgent:ks.createUserAgent(be.version).add({segment:"Node.js",version:process.versions.node})};return c.createSearchClient(g(u(u({},s),r),{methods:{search:c.multipleQueries,searchForFacetValues:c.multipleSearchForFacetValues,multipleBatch:c.multipleBatch,multipleGetObjects:c.multipleGetObjects,multipleQueries:c.multipleQueries,copyIndex:c.copyIndex,copySettings:c.copySettings,copyRules:c.copyRules,copySynonyms:c.copySynonyms,moveIndex:c.moveIndex,listIndices:c.listIndices,getLogs:c.getLogs,listClusters:c.listClusters,multipleSearchForFacetValues:c.multipleSearchForFacetValues,getApiKey:c.getApiKey,addApiKey:c.addApiKey,listApiKeys:c.listApiKeys,updateApiKey:c.updateApiKey,deleteApiKey:c.deleteApiKey,restoreApiKey:c.restoreApiKey,assignUserID:c.assignUserID,assignUserIDs:c.assignUserIDs,getUserID:c.getUserID,searchUserIDs:c.searchUserIDs,listUserIDs:c.listUserIDs,getTopUserIDs:c.getTopUserIDs,removeUserID:c.removeUserID,hasPendingMappings:c.hasPendingMappings,generateSecuredApiKey:c.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:c.getSecuredApiKeyRemainingValidity,destroy:be.destroy,initIndex:n=>a=>c.initIndex(n)(a,{methods:{batch:c.batch,delete:c.deleteIndex,getObject:c.getObject,getObjects:c.getObjects,saveObject:c.saveObject,saveObjects:c.saveObjects,search:c.search,searchForFacetValues:c.searchForFacetValues,waitTask:c.waitTask,setSettings:c.setSettings,getSettings:c.getSettings,partialUpdateObject:c.partialUpdateObject,partialUpdateObjects:c.partialUpdateObjects,deleteObject:c.deleteObject,deleteObjects:c.deleteObjects,deleteBy:c.deleteBy,clearObjects:c.clearObjects,browseObjects:c.browseObjects,getObjectPosition:c.getObjectPosition,findObject:c.findObject,exists:c.exists,saveSynonym:c.saveSynonym,saveSynonyms:c.saveSynonyms,getSynonym:c.getSynonym,searchSynonyms:c.searchSynonyms,browseSynonyms:c.browseSynonyms,deleteSynonym:c.deleteSynonym,clearSynonyms:c.clearSynonyms,replaceAllObjects:c.replaceAllObjects,replaceAllSynonyms:c.replaceAllSynonyms,searchRules:c.searchRules,getRule:c.getRule,deleteRule:c.deleteRule,saveRule:c.saveRule,saveRules:c.saveRules,replaceAllRules:c.replaceAllRules,browseRules:c.browseRules,clearRules:c.clearRules}}),initAnalytics:()=>n=>W.createAnalyticsClient(g(u(u({},s),n),{methods:{addABTest:W.addABTest,getABTest:W.getABTest,getABTests:W.getABTests,stopABTest:W.stopABTest,deleteABTest:W.deleteABTest}})),initRecommendation:()=>n=>Pe.createRecommendationClient(g(u(u({},s),n),{methods:{getPersonalizationStrategy:Pe.getPersonalizationStrategy,setPersonalizationStrategy:Pe.setPersonalizationStrategy}}))}}))}wt.version=be.version;Tt.exports=wt});var Ut=I((mn,je)=>{var Ct=kt();je.exports=Ct;je.exports.default=Ct});var Ws={};Vt(Ws,{default:()=>Ks});var Oe=C(require("@yarnpkg/core")),E=C(require("@yarnpkg/core")),Ie=C(require("@yarnpkg/plugin-essentials")),Ht=C(require("semver"));var se=C(require("@yarnpkg/core")),Nt=C(Ut()),Cs="e8e1bd300d860104bb8c58453ffa1eb4",Us="OFCNCOG2CU",Wt=async(e,t)=>{var a;let r=se.structUtils.stringifyIdent(e),n=Ns(t).initIndex("npm-search");try{return((a=(await n.getObject(r,{attributesToRetrieve:["types"]})).types)==null?void 0:a.ts)==="definitely-typed"}catch(o){return!1}},Ns=e=>(0,Nt.default)(Us,Cs,{requester:{async send(r){try{let s=await se.httpUtils.request(r.url,r.data||null,{configuration:e,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var _t=e=>e.scope?`${e.scope}__${e.name}`:`${e.name}`,Hs=async(e,t,r,s)=>{if(r.scope==="types")return;let{project:n}=e,{configuration:a}=n,o=a.makeResolver(),d={project:n,resolver:o,report:new E.ThrowReport};if(!await Wt(r,a))return;let b=_t(r),f=E.structUtils.parseRange(r.range).selector;if(!E.semverUtils.validRange(f)){let P=await o.getCandidates(r,new Map,d);f=E.structUtils.parseRange(P[0].reference).selector}let p=Ht.default.coerce(f);if(p===null)return;let h=`${Ie.suggestUtils.Modifier.CARET}${p.major}`,S=E.structUtils.makeDescriptor(E.structUtils.makeIdent("types",b),h),O=E.miscUtils.mapAndFind(n.workspaces,P=>{var T,V;let x=(T=P.manifest.dependencies.get(r.identHash))==null?void 0:T.descriptorHash,v=(V=P.manifest.devDependencies.get(r.identHash))==null?void 0:V.descriptorHash;if(x!==r.descriptorHash&&v!==r.descriptorHash)return E.miscUtils.mapAndFind.skip;let j=[];for(let Ae of Oe.Manifest.allDependencies){let Se=P.manifest[Ae].get(S.identHash);typeof Se!="undefined"&&j.push([Ae,Se])}return j.length===0?E.miscUtils.mapAndFind.skip:j});if(typeof O!="undefined")for(let[P,x]of O)e.manifest[P].set(x.identHash,x);else{try{if((await o.getCandidates(S,new Map,d)).length===0)return}catch{return}e.manifest[Ie.suggestUtils.Target.DEVELOPMENT].set(S.identHash,S)}},_s=async(e,t,r)=>{if(r.scope==="types")return;let s=_t(r),n=E.structUtils.makeIdent("types",s);for(let a of Oe.Manifest.allDependencies)typeof e.manifest[a].get(n.identHash)!="undefined"&&e.manifest[a].delete(n.identHash)},Fs=(e,t)=>{t.publishConfig&&t.publishConfig.typings&&(t.typings=t.publishConfig.typings),t.publishConfig&&t.publishConfig.types&&(t.types=t.publishConfig.types)},Bs={hooks:{afterWorkspaceDependencyAddition:Hs,afterWorkspaceDependencyRemoval:_s,beforeWorkspacePacking:Fs}},Ks=Bs;return Ws;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint your application uses 20 | module.exports = absRequire(`eslint`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "8.14.0-sdk", 4 | "main": "./lib/api.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.6.2-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const isPortal = str => str.startsWith("portal:/"); 22 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 23 | 24 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 25 | return `${locator.name}@${locator.reference}`; 26 | })); 27 | 28 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 29 | // doesn't understand. This layer makes sure to remove the protocol 30 | // before forwarding it to TS, and to add it back on all returned paths. 31 | 32 | function toEditorPath(str) { 33 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 34 | if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { 35 | // We also take the opportunity to turn virtual paths into physical ones; 36 | // this makes it much easier to work with workspaces that list peer 37 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 38 | // file instances instead of the real ones. 39 | // 40 | // We only do this to modules owned by the the dependency tree roots. 41 | // This avoids breaking the resolution when jumping inside a vendor 42 | // with peer dep (otherwise jumping into react-dom would show resolution 43 | // errors on react). 44 | // 45 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 46 | if (resolved) { 47 | const locator = pnpApi.findPackageLocator(resolved); 48 | if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { 49 | str = resolved; 50 | } 51 | } 52 | 53 | str = normalize(str); 54 | 55 | if (str.match(/\.zip\//)) { 56 | switch (hostInfo) { 57 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 58 | // VSCode only adds it automatically for supported schemes, 59 | // so we have to do it manually for the `zip` scheme. 60 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 61 | // 62 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 63 | // 64 | // Update 2021-10-08: VSCode changed their format in 1.61. 65 | // Before | ^zip:/c:/foo/bar.zip/package.json 66 | // After | ^/zip//c:/foo/bar.zip/package.json 67 | // 68 | // Update 2022-04-06: VSCode changed the format in 1.66. 69 | // Before | ^/zip//c:/foo/bar.zip/package.json 70 | // After | ^/zip/c:/foo/bar.zip/package.json 71 | // 72 | case `vscode <1.61`: { 73 | str = `^zip:${str}`; 74 | } break; 75 | 76 | case `vscode <1.66`: { 77 | str = `^/zip/${str}`; 78 | } break; 79 | 80 | case `vscode`: { 81 | str = `^/zip${str}`; 82 | } break; 83 | 84 | // To make "go to definition" work, 85 | // We have to resolve the actual file system path from virtual path 86 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 87 | case `coc-nvim`: { 88 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 89 | str = resolve(`zipfile:${str}`); 90 | } break; 91 | 92 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 93 | // We have to resolve the actual file system path from virtual path, 94 | // everything else is up to neovim 95 | case `neovim`: { 96 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 97 | str = `zipfile://${str}`; 98 | } break; 99 | 100 | default: { 101 | str = `zip:${str}`; 102 | } break; 103 | } 104 | } 105 | } 106 | 107 | return str; 108 | } 109 | 110 | function fromEditorPath(str) { 111 | switch (hostInfo) { 112 | case `coc-nvim`: { 113 | str = str.replace(/\.zip::/, `.zip/`); 114 | // The path for coc-nvim is in format of //zipfile://.yarn/... 115 | // So in order to convert it back, we use .* to match all the thing 116 | // before `zipfile:` 117 | return process.platform === `win32` 118 | ? str.replace(/^.*zipfile:\//, ``) 119 | : str.replace(/^.*zipfile:/, ``); 120 | } break; 121 | 122 | case `neovim`: { 123 | str = str.replace(/\.zip::/, `.zip/`); 124 | // The path for neovim is in format of zipfile:////.yarn/... 125 | return str.replace(/^zipfile:\/\//, ``); 126 | } break; 127 | 128 | case `vscode`: 129 | default: { 130 | return process.platform === `win32` 131 | ? str.replace(/^\^?(zip:|\/zip)\/+/, ``) 132 | : str.replace(/^\^?(zip:|\/zip)\/+/, `/`); 133 | } break; 134 | } 135 | } 136 | 137 | // Force enable 'allowLocalPluginLoads' 138 | // TypeScript tries to resolve plugins using a path relative to itself 139 | // which doesn't work when using the global cache 140 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 141 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 142 | // TypeScript already does local loads and if this code is running the user trusts the workspace 143 | // https://github.com/microsoft/vscode/issues/45856 144 | const ConfiguredProject = tsserver.server.ConfiguredProject; 145 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 146 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 147 | this.projectService.allowLocalPluginLoads = true; 148 | return originalEnablePluginsWithOptions.apply(this, arguments); 149 | }; 150 | 151 | // And here is the point where we hijack the VSCode <-> TS communications 152 | // by adding ourselves in the middle. We locate everything that looks 153 | // like an absolute path of ours and normalize it. 154 | 155 | const Session = tsserver.server.Session; 156 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 157 | let hostInfo = `unknown`; 158 | 159 | Object.assign(Session.prototype, { 160 | onMessage(/** @type {string | object} */ message) { 161 | const isStringMessage = typeof message === 'string'; 162 | const parsedMessage = isStringMessage ? JSON.parse(message) : message; 163 | 164 | if ( 165 | parsedMessage != null && 166 | typeof parsedMessage === `object` && 167 | parsedMessage.arguments && 168 | typeof parsedMessage.arguments.hostInfo === `string` 169 | ) { 170 | hostInfo = parsedMessage.arguments.hostInfo; 171 | if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { 172 | if (/(\/|-)1\.([1-5][0-9]|60)\./.test(process.env.VSCODE_IPC_HOOK)) { 173 | hostInfo += ` <1.61`; 174 | } else if (/(\/|-)1\.(6[1-5])\./.test(process.env.VSCODE_IPC_HOOK)) { 175 | hostInfo += ` <1.66`; 176 | } 177 | } 178 | } 179 | 180 | const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { 181 | return typeof value === 'string' ? fromEditorPath(value) : value; 182 | }); 183 | 184 | return originalOnMessage.call( 185 | this, 186 | isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) 187 | ); 188 | }, 189 | 190 | send(/** @type {any} */ msg) { 191 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 192 | return typeof value === `string` ? toEditorPath(value) : value; 193 | }))); 194 | } 195 | }); 196 | 197 | return tsserver; 198 | }; 199 | 200 | if (existsSync(absPnpApiPath)) { 201 | if (!process.versions.pnp) { 202 | // Setup the environment to be able to require typescript/lib/tsserver.js 203 | require(absPnpApiPath).setup(); 204 | } 205 | } 206 | 207 | // Defer to the real typescript/lib/tsserver.js your application uses 208 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); 209 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserverlibrary.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const isPortal = str => str.startsWith("portal:/"); 22 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 23 | 24 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 25 | return `${locator.name}@${locator.reference}`; 26 | })); 27 | 28 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 29 | // doesn't understand. This layer makes sure to remove the protocol 30 | // before forwarding it to TS, and to add it back on all returned paths. 31 | 32 | function toEditorPath(str) { 33 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 34 | if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { 35 | // We also take the opportunity to turn virtual paths into physical ones; 36 | // this makes it much easier to work with workspaces that list peer 37 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 38 | // file instances instead of the real ones. 39 | // 40 | // We only do this to modules owned by the the dependency tree roots. 41 | // This avoids breaking the resolution when jumping inside a vendor 42 | // with peer dep (otherwise jumping into react-dom would show resolution 43 | // errors on react). 44 | // 45 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 46 | if (resolved) { 47 | const locator = pnpApi.findPackageLocator(resolved); 48 | if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { 49 | str = resolved; 50 | } 51 | } 52 | 53 | str = normalize(str); 54 | 55 | if (str.match(/\.zip\//)) { 56 | switch (hostInfo) { 57 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 58 | // VSCode only adds it automatically for supported schemes, 59 | // so we have to do it manually for the `zip` scheme. 60 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 61 | // 62 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 63 | // 64 | // Update 2021-10-08: VSCode changed their format in 1.61. 65 | // Before | ^zip:/c:/foo/bar.zip/package.json 66 | // After | ^/zip//c:/foo/bar.zip/package.json 67 | // 68 | // Update 2022-04-06: VSCode changed the format in 1.66. 69 | // Before | ^/zip//c:/foo/bar.zip/package.json 70 | // After | ^/zip/c:/foo/bar.zip/package.json 71 | // 72 | case `vscode <1.61`: { 73 | str = `^zip:${str}`; 74 | } break; 75 | 76 | case `vscode <1.66`: { 77 | str = `^/zip/${str}`; 78 | } break; 79 | 80 | case `vscode`: { 81 | str = `^/zip${str}`; 82 | } break; 83 | 84 | // To make "go to definition" work, 85 | // We have to resolve the actual file system path from virtual path 86 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 87 | case `coc-nvim`: { 88 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 89 | str = resolve(`zipfile:${str}`); 90 | } break; 91 | 92 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 93 | // We have to resolve the actual file system path from virtual path, 94 | // everything else is up to neovim 95 | case `neovim`: { 96 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 97 | str = `zipfile://${str}`; 98 | } break; 99 | 100 | default: { 101 | str = `zip:${str}`; 102 | } break; 103 | } 104 | } 105 | } 106 | 107 | return str; 108 | } 109 | 110 | function fromEditorPath(str) { 111 | switch (hostInfo) { 112 | case `coc-nvim`: { 113 | str = str.replace(/\.zip::/, `.zip/`); 114 | // The path for coc-nvim is in format of //zipfile://.yarn/... 115 | // So in order to convert it back, we use .* to match all the thing 116 | // before `zipfile:` 117 | return process.platform === `win32` 118 | ? str.replace(/^.*zipfile:\//, ``) 119 | : str.replace(/^.*zipfile:/, ``); 120 | } break; 121 | 122 | case `neovim`: { 123 | str = str.replace(/\.zip::/, `.zip/`); 124 | // The path for neovim is in format of zipfile:////.yarn/... 125 | return str.replace(/^zipfile:\/\//, ``); 126 | } break; 127 | 128 | case `vscode`: 129 | default: { 130 | return process.platform === `win32` 131 | ? str.replace(/^\^?(zip:|\/zip)\/+/, ``) 132 | : str.replace(/^\^?(zip:|\/zip)\/+/, `/`); 133 | } break; 134 | } 135 | } 136 | 137 | // Force enable 'allowLocalPluginLoads' 138 | // TypeScript tries to resolve plugins using a path relative to itself 139 | // which doesn't work when using the global cache 140 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 141 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 142 | // TypeScript already does local loads and if this code is running the user trusts the workspace 143 | // https://github.com/microsoft/vscode/issues/45856 144 | const ConfiguredProject = tsserver.server.ConfiguredProject; 145 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 146 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 147 | this.projectService.allowLocalPluginLoads = true; 148 | return originalEnablePluginsWithOptions.apply(this, arguments); 149 | }; 150 | 151 | // And here is the point where we hijack the VSCode <-> TS communications 152 | // by adding ourselves in the middle. We locate everything that looks 153 | // like an absolute path of ours and normalize it. 154 | 155 | const Session = tsserver.server.Session; 156 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 157 | let hostInfo = `unknown`; 158 | 159 | Object.assign(Session.prototype, { 160 | onMessage(/** @type {string | object} */ message) { 161 | const isStringMessage = typeof message === 'string'; 162 | const parsedMessage = isStringMessage ? JSON.parse(message) : message; 163 | 164 | if ( 165 | parsedMessage != null && 166 | typeof parsedMessage === `object` && 167 | parsedMessage.arguments && 168 | typeof parsedMessage.arguments.hostInfo === `string` 169 | ) { 170 | hostInfo = parsedMessage.arguments.hostInfo; 171 | if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { 172 | if (/(\/|-)1\.([1-5][0-9]|60)\./.test(process.env.VSCODE_IPC_HOOK)) { 173 | hostInfo += ` <1.61`; 174 | } else if (/(\/|-)1\.(6[1-5])\./.test(process.env.VSCODE_IPC_HOOK)) { 175 | hostInfo += ` <1.66`; 176 | } 177 | } 178 | } 179 | 180 | const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { 181 | return typeof value === 'string' ? fromEditorPath(value) : value; 182 | }); 183 | 184 | return originalOnMessage.call( 185 | this, 186 | isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) 187 | ); 188 | }, 189 | 190 | send(/** @type {any} */ msg) { 191 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 192 | return typeof value === `string` ? toEditorPath(value) : value; 193 | }))); 194 | } 195 | }); 196 | 197 | return tsserver; 198 | }; 199 | 200 | if (existsSync(absPnpApiPath)) { 201 | if (!process.versions.pnp) { 202 | // Setup the environment to be able to require typescript/lib/tsserverlibrary.js 203 | require(absPnpApiPath).setup(); 204 | } 205 | } 206 | 207 | // Defer to the real typescript/lib/tsserverlibrary.js your application uses 208 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); 209 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "4.6.3-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableGlobalCache: true 2 | 3 | nodeLinker: pnp 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 7 | spec: "@yarnpkg/plugin-interactive-tools" 8 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 9 | spec: "@yarnpkg/plugin-typescript" 10 | 11 | yarnPath: .yarn/releases/yarn-3.2.0.cjs 12 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | anchor_version = "0.24.2" 2 | solana_version = "1.9.16" 3 | 4 | [features] 5 | seeds = true 6 | 7 | [registry] 8 | url = "https://anchor.projectserum.com" 9 | 10 | [provider] 11 | cluster = "localnet" 12 | wallet = "tests/fixture-key.json" 13 | 14 | [scripts] 15 | test = "yarn mocha" 16 | 17 | [test.validator] 18 | url = "https://api.mainnet-beta.solana.com" 19 | 20 | [[test.validator.clone]] 21 | address = "CRATwLpu6YZEeiVq9ajjxs61wPQ9f29s1UoQR9siJCRs" 22 | 23 | [[test.validator.clone]] 24 | address = "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ" 25 | 26 | [[test.validator.clone]] 27 | address = "ARoWLTBWoWrKMvxEiaE2EH9DrWyV7mLpKywGDWxBGeq9" 28 | 29 | [[test.validator.clone]] 30 | address = "SPQR4kT3q2oUKEJes2L6NNSBCiPW9SfuhkuqC9bp6Sx" 31 | 32 | [[test.validator.clone]] 33 | address = "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB" 34 | 35 | [[test.validator.clone]] 36 | address = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV" 37 | 38 | [programs.mainnet] 39 | brrr = "BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm" 40 | bankman = "BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1" 41 | 42 | [programs.devnet] 43 | brrr = "BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm" 44 | bankman = "BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1" 45 | 46 | [programs.testnet] 47 | brrr = "BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm" 48 | bankman = "BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1" 49 | 50 | [programs.localnet] 51 | brrr = "BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm" 52 | bankman = "BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1" 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["programs/*", "programs/brrr/converter"] 3 | 4 | [profile.release] 5 | lto = "fat" 6 | codegen-units = 1 7 | 8 | [profile.release.build-override] 9 | opt-level = 3 10 | incremental = false 11 | codegen-units = 1 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cashio 2 | 3 | [![License](https://img.shields.io/crates/l/bankman)](https://github.com/cashioapp/cashio/blob/master/LICENSE.md) 4 | [![Build Status](https://img.shields.io/github/workflow/status/cashioapp/cashio/E2E/master)](https://github.com/cashioapp/cashio/actions/workflows/programs-e2e.yml?query=branch%3Amaster) 5 | [![Contributors](https://img.shields.io/github/contributors/cashioapp/cashio)](https://github.com/cashioapp/cashio/graphs/contributors) 6 | [![Chat](https://img.shields.io/badge/chat-on%20keybase-success)](https://keybase.io/team/cashiochat) 7 | 8 | ![Cashio](/images/cashio.png) 9 | 10 | cashio is a decentralized stablecoin made for the people, by the people. 11 | 12 | We're in active development. For the latest updates, please join our community: 13 | 14 | - Twitter: https://twitter.com/cashioapp 15 | - Discord: https://discord.gg/5Mvhfc8vnX 16 | 17 | ## About 18 | 19 | Cashio is a decentralized stablecoin fully backed by interest-bearing [Saber](https://saber.so) USD liquidity provider tokens. Cashio specifically chooses USD LPs that are backed by safer USD assets, attempting to capture the risk-free rate of the Solana stablecoin ecosystem. 20 | 21 | Using [Arrow Protocol](https://arrowprotocol.com), Cashio stakes LP tokens into [Sunny Aggregator](https://sunny.ag), earning $SBR and $SUNNY tokens to the Cashio DAO. Cashio also uses [Crate Protocol](https://crateprotocol.com) to build its USD-pegged stablecoin, which can be thought of as a basket of stablecoin LPs. 22 | 23 | _Currently, protocol profits accrue to a program-owned account known as the Bank. We intend to create a mechanism to have these cash flows accrue value to users of the Cashio Protocol. More information on this will be available soon._ 24 | 25 | ## Packages 26 | 27 | | Package | Description | Version | Docs | 28 | | :--------------- | :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- | 29 | | `converter` | Math helpers for converting $CASH to/from Saber LP tokens. | [![Crates.io](https://img.shields.io/crates/v/converter)](https://crates.io/crates/converter) | [![Docs.rs](https://docs.rs/converter/badge.svg)](https://docs.rs/converter) | 30 | | `brrr` | Handles the printing and burning of $CASH, using Saber LP Arrows as collateral. | [![Crates.io](https://img.shields.io/crates/v/brrr)](https://crates.io/crates/brrr) | [![Docs.rs](https://docs.rs/brrr/badge.svg)](https://docs.rs/brrr) | 31 | | `bankman` | Allowlist for $CASH collateral tokens. | [![Crates.io](https://img.shields.io/crates/v/bankman)](https://crates.io/crates/bankman) | [![Docs.rs](https://docs.rs/bankman/badge.svg)](https://docs.rs/cashio) | 32 | | `@cashio/cashio` | TypeScript SDK for Cashio | [![npm](https://img.shields.io/npm/v/@cashio/cashio.svg)](https://www.npmjs.com/package/@cashio/cashio) | [![Docs](https://img.shields.io/badge/docs-typedoc-blue)](https://docs.cashio.app/ts/) | 33 | 34 | ## Note 35 | 36 | - **Cashio is in active development, so all APIs are subject to change.** 37 | - **This code is unaudited. Use at your own risk.** 38 | 39 | ## Contribution 40 | 41 | Thank you for your interest in contributing to Cashio Protocol! All contributions are welcome no 42 | matter how big or small. This includes (but is not limited to) filing issues, 43 | adding documentation, fixing bugs, creating examples, and implementing features. 44 | 45 | If you'd like to contribute, please claim an issue by commenting, forking, and 46 | opening a pull request, even if empty. This allows the maintainers to track who 47 | is working on what issue as to not overlap work. 48 | 49 | For simple documentation changes, feel free to just open a pull request. 50 | 51 | If you're considering larger changes or self motivated features, please file an issue 52 | and engage with the maintainers by joining the development channel on [Keybase](https://keybase.io/team/cashiochat). 53 | 54 | ## License 55 | 56 | Cashio Protocol is licensed under [the Affero GPL 3.0 license](/LICENSE.txt). 57 | 58 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Cashio Protocol by you, as defined in the AGPL-3.0 license, shall be licensed as above, without any additional terms or conditions. 59 | -------------------------------------------------------------------------------- /ci.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | pkgs.buildEnv { 3 | name = "ci"; 4 | paths = with pkgs; 5 | (pkgs.lib.optionals pkgs.stdenv.isLinux [ udev ]) ++ [ 6 | anchor-0_24_2 7 | solana-1_9-basic 8 | cargo-workspaces 9 | 10 | nodejs 11 | yarn 12 | python3 13 | 14 | pkgconfig 15 | openssl 16 | jq 17 | gnused 18 | 19 | libiconv 20 | ] ++ (pkgs.lib.optionals pkgs.stdenv.isDarwin [ 21 | pkgs.darwin.apple_sdk.frameworks.AppKit 22 | pkgs.darwin.apple_sdk.frameworks.IOKit 23 | pkgs.darwin.apple_sdk.frameworks.Foundation 24 | ]); 25 | } 26 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1649676176, 6 | "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-utils_2": { 19 | "locked": { 20 | "lastModified": 1649676176, 21 | "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", 22 | "owner": "numtide", 23 | "repo": "flake-utils", 24 | "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "type": "github" 31 | } 32 | }, 33 | "flake-utils_3": { 34 | "locked": { 35 | "lastModified": 1637014545, 36 | "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", 37 | "owner": "numtide", 38 | "repo": "flake-utils", 39 | "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", 40 | "type": "github" 41 | }, 42 | "original": { 43 | "owner": "numtide", 44 | "repo": "flake-utils", 45 | "type": "github" 46 | } 47 | }, 48 | "nixpkgs": { 49 | "locked": { 50 | "lastModified": 1650676489, 51 | "narHash": "sha256-8v0qMwq36v/mUGywsJTcfpm9HHMr4v2urYHns1LwPkg=", 52 | "owner": "NixOS", 53 | "repo": "nixpkgs", 54 | "rev": "875b41570c41386017d787caefedc008ddb6e31b", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "NixOS", 59 | "ref": "nixpkgs-unstable", 60 | "repo": "nixpkgs", 61 | "type": "github" 62 | } 63 | }, 64 | "nixpkgs_2": { 65 | "locked": { 66 | "lastModified": 1649961138, 67 | "narHash": "sha256-8ZCPrazs+qd2V8Elw84lIWuk0kKfVQ8Ei/19gahURhM=", 68 | "owner": "NixOS", 69 | "repo": "nixpkgs", 70 | "rev": "d08394e7cd5c7431a1e8f53b7f581e74ee909548", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "NixOS", 75 | "ref": "nixpkgs-unstable", 76 | "repo": "nixpkgs", 77 | "type": "github" 78 | } 79 | }, 80 | "root": { 81 | "inputs": { 82 | "flake-utils": "flake-utils", 83 | "nixpkgs": "nixpkgs", 84 | "saber-overlay": "saber-overlay" 85 | } 86 | }, 87 | "rust-overlay": { 88 | "inputs": { 89 | "flake-utils": "flake-utils_3", 90 | "nixpkgs": [ 91 | "saber-overlay", 92 | "nixpkgs" 93 | ] 94 | }, 95 | "locked": { 96 | "lastModified": 1649903781, 97 | "narHash": "sha256-m+3EZo0a4iS8IwHQhkM/riPuFpu76505xKqmN9j5O+E=", 98 | "owner": "oxalica", 99 | "repo": "rust-overlay", 100 | "rev": "e45696bedc4a13a5970376b8fc09660fdd0e6f6c", 101 | "type": "github" 102 | }, 103 | "original": { 104 | "owner": "oxalica", 105 | "repo": "rust-overlay", 106 | "type": "github" 107 | } 108 | }, 109 | "saber-overlay": { 110 | "inputs": { 111 | "flake-utils": "flake-utils_2", 112 | "nixpkgs": "nixpkgs_2", 113 | "rust-overlay": "rust-overlay" 114 | }, 115 | "locked": { 116 | "lastModified": 1649978970, 117 | "narHash": "sha256-hj+Yp3iacTNU/5+EhzcQ3xASiaifHP5AW3752vLMAn0=", 118 | "owner": "saber-hq", 119 | "repo": "saber-overlay", 120 | "rev": "5ec6426c8cc205d0577660fac5469f47f2dccabf", 121 | "type": "github" 122 | }, 123 | "original": { 124 | "owner": "saber-hq", 125 | "repo": "saber-overlay", 126 | "type": "github" 127 | } 128 | } 129 | }, 130 | "root": "root", 131 | "version": 7 132 | } 133 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Cashio development environment."; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | saber-overlay.url = "github:saber-hq/saber-overlay"; 7 | flake-utils.url = "github:numtide/flake-utils"; 8 | }; 9 | 10 | outputs = { self, nixpkgs, saber-overlay, flake-utils }: 11 | flake-utils.lib.eachSystem [ 12 | "aarch64-darwin" 13 | "x86_64-linux" 14 | "x86_64-darwin" 15 | ] 16 | (system: 17 | let 18 | pkgs = import nixpkgs { inherit system; } 19 | // saber-overlay.packages.${system}; 20 | in 21 | { 22 | devShell = import ./shell.nix { inherit pkgs; }; 23 | packages.ci = import ./ci.nix { inherit pkgs; }; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /images/cashio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashioapp/cashio/3f2c353029297fd91fd05622bb554b7c17d56fa6/images/cashio.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cashio/cashio", 3 | "version": "0.3.1", 4 | "description": "cashio is a decentralized stablecoin made for the people, by the people.", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "repository": "https://github.com/cashioapp/cashio", 8 | "author": "Ghost Chain ", 9 | "bugs": { 10 | "url": "https://github.com/cashioapp/cashio/issues", 11 | "email": "team@cashio.app" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "homepage": "https://cashio.app", 17 | "license": "AGPL-3.0", 18 | "devDependencies": { 19 | "@arrowprotocol/arrow": "^0.3.1", 20 | "@crateprotocol/crate-sdk": "^0.6.0", 21 | "@project-serum/anchor": "^0.24.2", 22 | "@quarryprotocol/quarry-sdk": "^5.0.2", 23 | "@rushstack/eslint-patch": "^1.1.3", 24 | "@saberhq/anchor-contrib": "^1.12.66", 25 | "@saberhq/chai-solana": "^1.12.66", 26 | "@saberhq/eslint-config": "^1.12.66", 27 | "@saberhq/solana-contrib": "^1.12.66", 28 | "@saberhq/stableswap-sdk": "^1.12.66", 29 | "@saberhq/token-utils": "^1.12.66", 30 | "@saberhq/tsconfig": "^1.12.66", 31 | "@solana/web3.js": "^1.39.1", 32 | "@types/bn.js": "^5.1.0", 33 | "@types/mocha": "^9.1.1", 34 | "@types/node": "^16.11.27", 35 | "@types/prettier": "^2.6.0", 36 | "bn.js": "^5.2.0", 37 | "chai": "^4.3.4", 38 | "eslint": "^8.14.0", 39 | "eslint-import-resolver-node": "^0.3.6", 40 | "eslint-plugin-import": "^2.26.0", 41 | "husky": "^7.0.4", 42 | "jsbi": "^4.3.0", 43 | "lint-staged": "^12.4.0", 44 | "mocha": "^9.2.2", 45 | "prettier": "^2.6.2", 46 | "ts-node": "^10.7.0", 47 | "typedoc": "^0.22.15", 48 | "typescript": "^4.6.3" 49 | }, 50 | "scripts": { 51 | "build": "rm -fr dist/ && tsc -P tsconfig.build.json && tsc -P tsconfig.esm.json", 52 | "docs:generate": "typedoc --excludePrivate --includeVersion --out site/ts/ src/index.ts", 53 | "typecheck": "tsc", 54 | "idl:generate": "./scripts/parse-idls.sh && ./scripts/generate-idl-types.sh", 55 | "idl:generate:nolint": "./scripts/parse-idls.sh && RUN_ESLINT=none ./scripts/generate-idl-types.sh", 56 | "lint": "eslint . --cache", 57 | "test:e2e": "anchor test --skip-build tests/*.ts", 58 | "prepare": "husky install" 59 | }, 60 | "peerDependencies": { 61 | "@arrowprotocol/arrow": "^0.3", 62 | "@crateprotocol/crate-sdk": "^0.6", 63 | "@project-serum/anchor": "^0.24", 64 | "@quarryprotocol/quarry-sdk": "^5", 65 | "@saberhq/anchor-contrib": "^1.12", 66 | "@saberhq/solana-contrib": "^1.12", 67 | "@saberhq/stableswap-sdk": "^1.12", 68 | "@saberhq/token-utils": "^1.12", 69 | "@solana/web3.js": "^1.37", 70 | "bn.js": "^5.2.0" 71 | }, 72 | "packageManager": "yarn@3.2.0", 73 | "dependencies": { 74 | "superstruct": "^0.15.4", 75 | "tiny-invariant": "^1.2.0", 76 | "tslib": "^2.4.0" 77 | }, 78 | "lint-staged": { 79 | "*.ts": "eslint --cache --fix", 80 | "*.{md,json,js,yml,yaml}": "prettier --write" 81 | }, 82 | "files": [ 83 | "dist/", 84 | "src/" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /programs/bankman/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bankman" 3 | version = "0.3.1" 4 | description = "Allowlist for $CASH collateral tokens." 5 | edition = "2021" 6 | homepage = "https://cashio.app" 7 | repository = "https://github.com/CashioApp/cashio" 8 | authors = ["Ghost Chain "] 9 | license = "AGPL-3.0" 10 | keywords = ["solana", "anchor", "crate", "cashio", "saber"] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | name = "bankman" 15 | 16 | [features] 17 | no-entrypoint = [] 18 | no-idl = [] 19 | cpi = ["no-entrypoint"] 20 | default = [] 21 | 22 | [dependencies] 23 | anchor-lang = "^0.24" 24 | anchor-spl = "^0.24" 25 | crate-token = { version = "^0.6", features = ["cpi"] } 26 | vipers = "^2" 27 | -------------------------------------------------------------------------------- /programs/bankman/README.md: -------------------------------------------------------------------------------- 1 | # `bankman` 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/bankman)](https://crates.io/crates/bankman) 4 | [![Docs.rs](https://docs.rs/bankman/badge.svg)](https://docs.rs/bankman) 5 | [![License](https://img.shields.io/badge/license-AGPL)](https://github.com/cashioapp/cashio/blob/master/LICENSE.txt) 6 | 7 | Allowlist for $CASH collateral tokens. 8 | 9 | The Bank manager, or `bankman` for short, keeps track of the collateral that is allowed to be used as a backing for the $CASH token. 10 | 11 | This program itself does not have the ability to print or burn $CASH. Instead, it delegates this responsibility to the `brrr` program, which acts as the [Crate](https://crate.so) `issue_authority` and `withdraw_authority`. 12 | 13 | ## Addresses 14 | 15 | The program address is the same on devnet, testnet, and mainnet-beta. 16 | 17 | Program Address: [`BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1`](https://explorer.solana.com/address/BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1) 18 | -------------------------------------------------------------------------------- /programs/bankman/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/bankman/src/account_validators.rs: -------------------------------------------------------------------------------- 1 | //! Validate accounts 2 | 3 | use crate::*; 4 | use anchor_lang::prelude::*; 5 | use vipers::assert_keys_eq; 6 | use vipers::validate::Validate; 7 | 8 | macro_rules! assert_is_curator { 9 | ($self: ident) => { 10 | assert_keys_eq!($self.curator, $self.bank.curator, UnauthorizedNotCurator); 11 | }; 12 | } 13 | 14 | macro_rules! assert_is_bankman { 15 | ($self: ident) => { 16 | assert_keys_eq!($self.bankman, $self.bank.bankman, UnauthorizedNotBankman); 17 | }; 18 | } 19 | 20 | impl<'info> Validate<'info> for NewBank<'info> { 21 | fn validate(&self) -> Result<()> { 22 | // brrr_issue_authority does not need to be validated because 23 | // the Bank is created once 24 | // burn_withdraw_authority does not need to be validated because 25 | // the Bank is created once 26 | require!(self.crate_mint.supply == 0, NewBankSupplyMustBeZero); 27 | require!( 28 | self.crate_mint.decimals == CASH_DECIMALS, 29 | NewBankWrongDecimals 30 | ); 31 | require!(self.crate_token.data_is_empty(), NewBankAlreadyInitialized); 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl<'info> Validate<'info> for AuthorizeCollateral<'info> { 37 | fn validate(&self) -> Result<()> { 38 | assert_is_curator!(self); 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl<'info> Validate<'info> for SetCurator<'info> { 44 | fn validate(&self) -> Result<()> { 45 | assert_is_bankman!(self); 46 | Ok(()) 47 | } 48 | } 49 | 50 | impl<'info> Validate<'info> for SetBankman<'info> { 51 | fn validate(&self) -> Result<()> { 52 | assert_is_bankman!(self); 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl<'info> Validate<'info> for SetCollateralHardCap<'info> { 58 | fn validate(&self) -> Result<()> { 59 | assert_is_curator!(self); 60 | assert_keys_eq!(self.collateral.bank, self.bank); 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /programs/bankman/src/events.rs: -------------------------------------------------------------------------------- 1 | //! Crate events 2 | #![deny(missing_docs)] 3 | 4 | use anchor_lang::prelude::*; 5 | 6 | /// Emitted when a [crate::Bank] is created. 7 | #[event] 8 | pub struct NewBankEvent { 9 | /// The [crate::Bank]. 10 | #[index] 11 | pub bank: Pubkey, 12 | /// Curator. 13 | pub curator: Pubkey, 14 | /// Timestamp of the event. 15 | pub timestamp: i64, 16 | } 17 | 18 | /// Emitted when a [crate::Collateral] is created. 19 | #[event] 20 | pub struct AddCollateralEvent { 21 | /// crate::Bank 22 | #[index] 23 | pub bank: Pubkey, 24 | /// Stake pool 25 | #[index] 26 | pub collateral: Pubkey, 27 | 28 | /// The [crate::Bank::curator]. 29 | pub curator: Pubkey, 30 | /// The [anchor_spl::token::Mint] of the collateral. 31 | pub mint: Pubkey, 32 | 33 | /// Timestamp of the event. 34 | pub timestamp: i64, 35 | } 36 | 37 | /// Emitted when an [crate::Bank]'s curator is modified. 38 | #[event] 39 | pub struct SetCuratorEvent { 40 | /// crate::Bank 41 | #[index] 42 | pub bank: Pubkey, 43 | 44 | /// The new [crate::Bank::curator]. 45 | pub curator: Pubkey, 46 | /// The previous [crate::Bank::curator]. 47 | pub previous_curator: Pubkey, 48 | /// The [crate::Bank::bankman]. 49 | pub bankman: Pubkey, 50 | 51 | /// Timestamp of the event. 52 | pub timestamp: i64, 53 | } 54 | 55 | /// Emitted when an [crate::Bank]'s bankman is modified. 56 | #[event] 57 | pub struct SetBankmanEvent { 58 | /// crate::Bank 59 | #[index] 60 | pub bank: Pubkey, 61 | 62 | /// The new [crate::Bank::bankman]. 63 | pub bankman: Pubkey, 64 | /// The previous [crate::Bank::bankman]. 65 | pub previous_bankman: Pubkey, 66 | 67 | /// Timestamp of the event. 68 | pub timestamp: i64, 69 | } 70 | 71 | /// Emitted when a [crate::Collateral]'s hard cap is modified. 72 | #[event] 73 | pub struct SetCollateralHardCapEvent { 74 | /// crate::Bank 75 | #[index] 76 | pub bank: Pubkey, 77 | /// Stake pool 78 | #[index] 79 | pub collateral: Pubkey, 80 | 81 | /// Hard cap 82 | pub hard_cap: u64, 83 | /// Timestamp of the event. 84 | pub timestamp: i64, 85 | } 86 | -------------------------------------------------------------------------------- /programs/bankman/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod withdraw_author_fee; 2 | 3 | pub use withdraw_author_fee::*; 4 | -------------------------------------------------------------------------------- /programs/bankman/src/instructions/withdraw_author_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_spl::token::{self, Token, TokenAccount}; 3 | 4 | /// Accounts for [bankman::withdraw_author_fee]. 5 | #[derive(Accounts)] 6 | pub struct WithdrawAuthorFee<'info> { 7 | /// The [Bank]. 8 | #[account(has_one = bankman @ crate::ErrorCode::UnauthorizedNotBankman)] 9 | pub bank: Account<'info, Bank>, 10 | /// The [Bank::bankman]. 11 | pub bankman: Signer<'info>, 12 | /// The [Collateral]. 13 | #[account(has_one = bank)] 14 | pub collateral: Account<'info, Collateral>, 15 | /// Author fees. 16 | #[account(mut, constraint = author_fees.mint == collateral.mint)] 17 | pub author_fees: Account<'info, TokenAccount>, 18 | /// Account to send the author fees to. 19 | #[account(mut)] 20 | pub destination: Account<'info, TokenAccount>, 21 | /// The [Token] program. 22 | pub token_program: Program<'info, Token>, 23 | } 24 | 25 | impl<'info> Validate<'info> for WithdrawAuthorFee<'info> { 26 | fn validate(&self) -> Result<()> { 27 | assert_keys_neq!(self.author_fees, self.destination); 28 | assert_keys_eq!(self.author_fees.owner, self.bank); 29 | assert_keys_eq!(self.author_fees.mint, self.collateral.mint); 30 | Ok(()) 31 | } 32 | } 33 | 34 | pub fn handler(ctx: Context, amount: u64) -> Result<()> { 35 | let signer_seeds: &[&[&[u8]]] = &[&[ 36 | b"Bank".as_ref(), 37 | &ctx.accounts.bank.crate_token.to_bytes(), 38 | &[ctx.accounts.bank.bump], 39 | ]]; 40 | token::transfer( 41 | CpiContext::new( 42 | ctx.accounts.token_program.to_account_info(), 43 | token::Transfer { 44 | from: ctx.accounts.author_fees.to_account_info(), 45 | to: ctx.accounts.destination.to_account_info(), 46 | authority: ctx.accounts.bank.to_account_info(), 47 | }, 48 | ) 49 | .with_signer(signer_seeds), 50 | amount, 51 | )?; 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /programs/bankman/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Allowlist for $CASH collateral tokens. 2 | #![deny(rustdoc::all)] 3 | #![allow(rustdoc::missing_doc_code_examples)] 4 | 5 | mod account_validators; 6 | mod events; 7 | mod instructions; 8 | mod state; 9 | 10 | use anchor_lang::prelude::*; 11 | use anchor_spl::token::Mint; 12 | use vipers::prelude::*; 13 | 14 | pub use events::*; 15 | use instructions::*; 16 | pub use state::*; 17 | 18 | /// Number of decimals of $CASH. 19 | pub const CASH_DECIMALS: u8 = 6; 20 | 21 | declare_id!("BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1"); 22 | 23 | /// [bankman] program. 24 | #[program] 25 | pub mod bankman { 26 | use super::*; 27 | 28 | /// Provisions a new [Bank]. 29 | #[access_control(ctx.accounts.validate())] 30 | pub fn new_bank(ctx: Context, _bank_bump: u8, crate_bump: u8) -> Result<()> { 31 | crate_token::cpi::new_crate( 32 | CpiContext::new( 33 | ctx.accounts.crate_token_program.to_account_info(), 34 | crate_token::cpi::accounts::NewCrate { 35 | crate_mint: ctx.accounts.crate_mint.to_account_info(), 36 | crate_token: ctx.accounts.crate_token.to_account_info(), 37 | fee_to_setter: ctx.accounts.bank.to_account_info(), 38 | fee_setter_authority: ctx.accounts.bank.to_account_info(), 39 | author_fee_to: ctx.accounts.bank.to_account_info(), 40 | issue_authority: ctx.accounts.brrr_issue_authority.to_account_info(), 41 | withdraw_authority: ctx.accounts.burn_withdraw_authority.to_account_info(), 42 | payer: ctx.accounts.payer.to_account_info(), 43 | system_program: ctx.accounts.system_program.to_account_info(), 44 | }, 45 | ), 46 | crate_bump, 47 | )?; 48 | 49 | let bank_bump = unwrap_bump!(ctx, "bank"); 50 | let signer_seeds: &[&[&[u8]]] = &[&[ 51 | b"Bank".as_ref(), 52 | &ctx.accounts.crate_token.key().to_bytes(), 53 | &[bank_bump], 54 | ]]; 55 | 56 | // Initial withdraw fee is 0.5% or 50 bps 57 | crate_token::cpi::set_withdraw_fee( 58 | CpiContext::new( 59 | ctx.accounts.crate_token_program.to_account_info(), 60 | crate_token::cpi::accounts::SetFees { 61 | crate_token: ctx.accounts.crate_token.to_account_info(), 62 | fee_setter: ctx.accounts.bank.to_account_info(), 63 | }, 64 | ) 65 | .with_signer(signer_seeds), 66 | 50, 67 | )?; 68 | 69 | let bank = &mut ctx.accounts.bank; 70 | bank.crate_token = ctx.accounts.crate_token.key(); 71 | bank.bump = bank_bump; 72 | 73 | bank.crate_mint = ctx.accounts.crate_mint.key(); 74 | bank.curator = ctx.accounts.admin.key(); 75 | bank.bankman = ctx.accounts.admin.key(); 76 | 77 | emit!(NewBankEvent { 78 | bank: bank.key(), 79 | curator: bank.curator, 80 | timestamp: Clock::get()?.unix_timestamp 81 | }); 82 | 83 | Ok(()) 84 | } 85 | 86 | /// Adds a new collateral pool to a [Bank]. 87 | #[access_control(ctx.accounts.validate())] 88 | pub fn authorize_collateral(ctx: Context, _bump: u8) -> Result<()> { 89 | let bank = &ctx.accounts.bank; 90 | 91 | let collateral = &mut ctx.accounts.collateral; 92 | collateral.bank = bank.key(); 93 | collateral.mint = ctx.accounts.mint.key(); 94 | collateral.bump = unwrap_bump!(ctx, "collateral"); 95 | 96 | emit!(AddCollateralEvent { 97 | bank: bank.key(), 98 | collateral: collateral.key(), 99 | curator: bank.curator, 100 | mint: collateral.mint, 101 | timestamp: Clock::get()?.unix_timestamp 102 | }); 103 | Ok(()) 104 | } 105 | 106 | /// Adds a new collateral pool to a [Bank]. 107 | #[access_control(ctx.accounts.validate())] 108 | pub fn set_collateral_hard_cap( 109 | ctx: Context, 110 | hard_cap: u64, 111 | ) -> Result<()> { 112 | let collateral = &mut ctx.accounts.collateral; 113 | collateral.hard_cap = hard_cap; 114 | 115 | emit!(SetCollateralHardCapEvent { 116 | bank: ctx.accounts.bank.key(), 117 | collateral: collateral.key(), 118 | hard_cap, 119 | timestamp: Clock::get()?.unix_timestamp 120 | }); 121 | Ok(()) 122 | } 123 | 124 | /// Sets the curator. 125 | #[access_control(ctx.accounts.validate())] 126 | pub fn set_curator(ctx: Context) -> Result<()> { 127 | let bank = &mut ctx.accounts.bank; 128 | let previous_curator = bank.curator; 129 | bank.curator = ctx.accounts.next_curator.key(); 130 | 131 | emit!(SetCuratorEvent { 132 | bank: bank.key(), 133 | previous_curator, 134 | curator: bank.curator, 135 | bankman: bank.bankman, 136 | timestamp: Clock::get()?.unix_timestamp 137 | }); 138 | 139 | Ok(()) 140 | } 141 | 142 | /// Sets the bankman. 143 | #[access_control(ctx.accounts.validate())] 144 | pub fn set_bankman(ctx: Context) -> Result<()> { 145 | let bank = &mut ctx.accounts.bank; 146 | let previous_bankman = bank.bankman; 147 | bank.bankman = ctx.accounts.next_bankman.key(); 148 | 149 | emit!(SetBankmanEvent { 150 | bank: bank.key(), 151 | previous_bankman, 152 | bankman: bank.bankman, 153 | timestamp: Clock::get()?.unix_timestamp 154 | }); 155 | 156 | Ok(()) 157 | } 158 | 159 | /// Withdraws the author fee to the specified location. 160 | #[access_control(ctx.accounts.validate())] 161 | pub fn withdraw_author_fee(ctx: Context, amount: u64) -> Result<()> { 162 | instructions::withdraw_author_fee::handler(ctx, amount) 163 | } 164 | } 165 | 166 | /// Accounts for [bankman::new_bank]. 167 | #[derive(Accounts)] 168 | pub struct NewBank<'info> { 169 | /// Information about the [Bank]. 170 | #[account( 171 | init, 172 | seeds = [ 173 | b"Bank".as_ref(), 174 | crate_token.key().to_bytes().as_ref() 175 | ], 176 | bump, 177 | space = 8 + Bank::BYTES, 178 | payer = payer 179 | )] 180 | pub bank: Account<'info, Bank>, 181 | 182 | /// [Mint] of the [crate_token::CrateToken]. 183 | pub crate_mint: Account<'info, Mint>, 184 | 185 | /// The [crate_token::CrateToken] to be created. 186 | #[account(mut)] 187 | pub crate_token: SystemAccount<'info>, 188 | 189 | /// The `brrr_issue_authority`. 190 | /// CHECK: Arbitrary. 191 | pub brrr_issue_authority: UncheckedAccount<'info>, 192 | 193 | /// The `burn_withdraw_authority`. 194 | /// CHECK: Arbitrary. 195 | pub burn_withdraw_authority: UncheckedAccount<'info>, 196 | 197 | /// Payer of the crate initialization. 198 | #[account(mut)] 199 | pub payer: Signer<'info>, 200 | 201 | /// The admin, who becomes the curator and the curator setter. 202 | /// CHECK: Arbitrary. 203 | pub admin: UncheckedAccount<'info>, 204 | 205 | /// System program. 206 | pub system_program: Program<'info, System>, 207 | 208 | /// Crate token program. 209 | pub crate_token_program: Program<'info, crate_token::program::CrateToken>, 210 | } 211 | 212 | /// Accounts for [bankman::authorize_collateral]. 213 | #[derive(Accounts)] 214 | pub struct AuthorizeCollateral<'info> { 215 | /// The [Bank]. 216 | pub bank: Account<'info, Bank>, 217 | 218 | /// The [Collateral] to add. 219 | #[account( 220 | init, 221 | seeds = [ 222 | b"Collateral".as_ref(), 223 | bank.key().to_bytes().as_ref(), 224 | mint.key().to_bytes().as_ref() 225 | ], 226 | bump, 227 | space = 8 + Collateral::BYTES, 228 | payer = payer 229 | )] 230 | pub collateral: Account<'info, Collateral>, 231 | 232 | /// [Mint] of the collateral. 233 | pub mint: Box>, 234 | 235 | /// The [Bank::curator]. 236 | pub curator: Signer<'info>, 237 | 238 | /// Payer of the crate initialization. 239 | #[account(mut)] 240 | pub payer: Signer<'info>, 241 | 242 | /// System program. 243 | pub system_program: Program<'info, System>, 244 | } 245 | 246 | /// Accounts for [bankman::set_collateral_hard_cap]. 247 | #[derive(Accounts)] 248 | pub struct SetCollateralHardCap<'info> { 249 | /// The [Bank]. 250 | pub bank: Account<'info, Bank>, 251 | /// The [Collateral]. 252 | #[account(mut)] 253 | pub collateral: Account<'info, Collateral>, 254 | /// The [Bank::curator]. 255 | pub curator: Signer<'info>, 256 | } 257 | 258 | /// Accounts for [bankman::set_curator]. 259 | #[derive(Accounts)] 260 | pub struct SetCurator<'info> { 261 | /// The [Bank]. 262 | #[account(mut)] 263 | pub bank: Account<'info, Bank>, 264 | /// The [Bank::bankman]. 265 | pub bankman: Signer<'info>, 266 | /// The [Bank::curator] to set. 267 | /// CHECK: Arbitrary. 268 | pub next_curator: UncheckedAccount<'info>, 269 | } 270 | 271 | /// Accounts for [bankman::set_bankman]. 272 | #[derive(Accounts)] 273 | pub struct SetBankman<'info> { 274 | /// The [Bank]. 275 | #[account(mut)] 276 | pub bank: Account<'info, Bank>, 277 | /// The [Bank::bankman]. 278 | pub bankman: Signer<'info>, 279 | /// The [Bank::curator] to set. 280 | /// CHECK: Arbitrary. 281 | pub next_bankman: UncheckedAccount<'info>, 282 | } 283 | 284 | /// Errors. 285 | #[error_code] 286 | pub enum ErrorCode { 287 | #[msg("Must be curator.")] 288 | UnauthorizedNotCurator, 289 | #[msg("Must be the bankman.")] 290 | UnauthorizedNotBankman, 291 | 292 | #[msg("Pool not found in snapshot.", offset = 10)] 293 | PoolNotFoundInSnapshot, 294 | #[msg("Cannot add a pool that has already been added.")] 295 | PoolAlreadyAdded, 296 | 297 | #[msg("new_bank: supply must be zero", offset = 20)] 298 | NewBankSupplyMustBeZero, 299 | #[msg("new_bank: cash must have 6 decimals")] 300 | NewBankWrongDecimals, 301 | #[msg("new_bank: crate already initialized")] 302 | NewBankAlreadyInitialized, 303 | } 304 | -------------------------------------------------------------------------------- /programs/bankman/src/state.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES}; 2 | 3 | /// 🏦 4 | /// 5 | /// Lets users print $CASH or redeem $CASH for its underlying. 6 | #[account] 7 | #[derive(Copy, Debug, Default, PartialEq, Eq)] 8 | pub struct Bank { 9 | /// The [crate_token::CrateToken]. 10 | pub crate_token: Pubkey, 11 | /// Bump. 12 | pub bump: u8, 13 | 14 | /// Mint of the [crate_token::CrateToken]. 15 | pub crate_mint: Pubkey, 16 | /// Account that can choose what collateral is allowed. 17 | pub curator: Pubkey, 18 | /// Account that can change who the curator is. 19 | pub bankman: Pubkey, 20 | } 21 | 22 | impl Bank { 23 | pub const BYTES: usize = PUBKEY_BYTES + 1 + PUBKEY_BYTES * 3; 24 | } 25 | 26 | /// The collateral which has been authorized to mint $CASH. 27 | #[account] 28 | #[derive(Copy, Debug, Default, PartialEq, Eq)] 29 | pub struct Collateral { 30 | /// The [Bank]. 31 | pub bank: Pubkey, 32 | /// Mint of the collateral. 33 | pub mint: Pubkey, 34 | /// The bump. 35 | pub bump: u8, 36 | /// Hard cap on the number of collateral tokens that can be issued from this pool. 37 | pub hard_cap: u64, 38 | } 39 | 40 | impl Collateral { 41 | pub const BYTES: usize = PUBKEY_BYTES * 2 + 1 + 8; 42 | } 43 | -------------------------------------------------------------------------------- /programs/brrr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brrr" 3 | version = "0.3.1" 4 | description = "Handles the printing and burning of $CASH, using Saber LP Arrows as collateral." 5 | edition = "2021" 6 | homepage = "https://cashio.app" 7 | repository = "https://github.com/CashioApp/cashio" 8 | authors = ["Ghost Chain "] 9 | license = "AGPL-3.0" 10 | keywords = ["solana", "anchor", "crate", "cashio", "saber"] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | name = "brrr" 15 | 16 | [features] 17 | no-entrypoint = [] 18 | no-idl = [] 19 | cpi = ["no-entrypoint"] 20 | default = [] 21 | 22 | [dependencies] 23 | anchor-lang = "^0.24" 24 | anchor-spl = "^0.24" 25 | arrow-sunny = { version = "^0.3", features = ["cpi"] } 26 | crate-token = { version = "^0.6", features = ["cpi"] } 27 | stable-swap-anchor = "^1.8" 28 | static-pubkey = "1.0.2" 29 | vipers = "^2" 30 | bankman = { path = "../bankman", version = "0.3.0", features = ["cpi"] } 31 | converter = { path = "./converter", version = "0.3.0" } 32 | -------------------------------------------------------------------------------- /programs/brrr/README.md: -------------------------------------------------------------------------------- 1 | # 🖨 brrr 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/brrr)](https://crates.io/crates/brrr) 4 | [![Docs.rs](https://docs.rs/brrr/badge.svg)](https://docs.rs/brrr) 5 | [![License](https://img.shields.io/badge/license-AGPL)](https://github.com/CashioApp/cashio/blob/master/LICENSE.txt) 6 | 7 | Handles the printing and burning of $CASH, using [Saber LP](https://saber.so) [Arrows](https://arrowprotocol.com) as collateral. 8 | 9 | ## Addresses 10 | 11 | The program address is the same on devnet, testnet, and mainnet-beta. 12 | 13 | Program Address: [`BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm`](https://explorer.solana.com/address/BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm) 14 | 15 | ## Mechanism 16 | 17 | There are two instructions that handle each of these actions: `print_cash` and `burn_cash`. 18 | 19 | ### `print_cash` 20 | 21 | This instruction prints $CASH in exchange for Arrow Saber LP tokens. 22 | 23 | The issue authority of `print_cash` is `BJ9L3jNu6tvrUxPHTMfwyA8Lgw2X6ky5bVNyDqiXSxgA`. 24 | 25 | ### `burn_cash` 26 | 27 | This instruction burns $CASH in exchange for Arrow Saber LP tokens. 28 | 29 | The withdraw authority of `burn_cash` is `7Twx9JYz3gB4rF3h2cyUMnQWj9QEtmwviTvVD7xjAGEw`. 30 | -------------------------------------------------------------------------------- /programs/brrr/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/brrr/converter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "converter" 3 | version = "0.3.1" 4 | description = "Math helpers for converting $CASH to/from Saber LP tokens." 5 | edition = "2021" 6 | homepage = "https://cashio.app" 7 | repository = "https://github.com/cashioapp/cashio" 8 | authors = ["Ghost Chain "] 9 | license = "AGPL-3.0" 10 | keywords = ["solana", "cashio", "saber"] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | name = "converter" 15 | 16 | [dependencies] 17 | stable-swap-math = "^1.8" 18 | -------------------------------------------------------------------------------- /programs/brrr/converter/README.md: -------------------------------------------------------------------------------- 1 | # converter 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/converter)](https://crates.io/crates/converter) 4 | [![Docs.rs](https://docs.rs/converter/badge.svg)](https://docs.rs/converter) 5 | [![License](https://img.shields.io/badge/license-AGPL)](https://github.com/CashioApp/cashio/blob/master/LICENSE.txt) 6 | 7 | Math helpers for converting $CASH to/from Saber LP tokens. 8 | -------------------------------------------------------------------------------- /programs/brrr/converter/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Saber swap math helpers for $CASH 2 | #![deny(missing_docs)] 3 | #![deny(rustdoc::all)] 4 | #![allow(rustdoc::missing_doc_code_examples)] 5 | #![deny(clippy::integer_arithmetic)] 6 | 7 | use std::cmp::Ordering; 8 | 9 | /// Number of decimals of $CASH. 10 | pub const CASH_DECIMALS: u8 = 6; 11 | 12 | pub use stable_swap_math::price::SaberSwap; 13 | 14 | /// A Saber swap and number of decimals. 15 | #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] 16 | pub struct CashSwap { 17 | /// Decimals of the LP mint. 18 | /// This is used for $CASH conversion. 19 | pub lp_mint_decimals: u8, 20 | /// Saber. 21 | pub saber: SaberSwap, 22 | } 23 | 24 | impl CashSwap { 25 | /// Calculates the virtual price of the given amount of pool tokens. 26 | pub fn calculate_cash_for_pool_tokens(&self, pool_token_amount: u64) -> Option { 27 | self.scale_lp_to_cash_decimals( 28 | self.saber 29 | .calculate_virtual_price_of_pool_tokens(pool_token_amount)?, 30 | ) 31 | } 32 | 33 | /// Calculates the virtual price of the given amount of pool tokens. 34 | pub fn calculate_pool_tokens_for_cash(&self, cash_amount: u64) -> Option { 35 | self.saber 36 | .calculate_pool_tokens_from_virtual_amount(self.scale_cash_to_lp_decimals(cash_amount)?) 37 | } 38 | 39 | fn scale_lp_to_cash_decimals(&self, amount: u64) -> Option { 40 | match CASH_DECIMALS.cmp(&self.lp_mint_decimals) { 41 | Ordering::Equal => amount.into(), 42 | Ordering::Less => amount.checked_mul( 43 | 10u64.checked_pow(self.lp_mint_decimals.checked_sub(CASH_DECIMALS)?.into())?, 44 | ), 45 | Ordering::Greater => amount.checked_div( 46 | 10u64.checked_pow(CASH_DECIMALS.checked_sub(self.lp_mint_decimals)?.into())?, 47 | ), 48 | } 49 | } 50 | 51 | fn scale_cash_to_lp_decimals(&self, amount: u64) -> Option { 52 | match CASH_DECIMALS.cmp(&self.lp_mint_decimals) { 53 | Ordering::Equal => amount.into(), 54 | Ordering::Less => amount.checked_div( 55 | 10u64.checked_pow(self.lp_mint_decimals.checked_sub(CASH_DECIMALS)?.into())?, 56 | ), 57 | Ordering::Greater => amount.checked_mul( 58 | 10u64.checked_pow(CASH_DECIMALS.checked_sub(self.lp_mint_decimals)?.into())?, 59 | ), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /programs/brrr/src/actions/burn_cash.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::*; 4 | use anchor_lang::prelude::*; 5 | use converter::CashSwap; 6 | use vipers::{assert_keys_eq, unwrap_int, validate::Validate}; 7 | 8 | /// Prints $CASH. 9 | pub fn burn_cash(ctx: Context, burn_amount: u64) -> Result<()> { 10 | ctx.accounts.burn_cash(burn_amount) 11 | } 12 | 13 | impl<'info> BurnCash<'info> { 14 | /// We like the $CASH. 15 | fn burn_cash(&self, burn_amount: u64) -> Result<()> { 16 | let swap: CashSwap = (&self.common.saber_swap).try_into()?; 17 | let withdraw_pool_token_amount = 18 | unwrap_int!(swap.calculate_pool_tokens_for_cash(burn_amount)); 19 | if withdraw_pool_token_amount == 0 { 20 | return Ok(()); 21 | } 22 | 23 | let current_balance = self.common.crate_collateral_tokens.amount; 24 | require!( 25 | current_balance >= withdraw_pool_token_amount, 26 | InsufficientFunds 27 | ); 28 | 29 | // Burn the $CASH. 30 | anchor_spl::token::burn( 31 | CpiContext::new( 32 | self.common.token_program.to_account_info(), 33 | anchor_spl::token::Burn { 34 | mint: self.common.crate_mint.to_account_info(), 35 | from: self.burned_cash_source.to_account_info(), 36 | authority: self.burner.to_account_info(), 37 | }, 38 | ), 39 | burn_amount, 40 | )?; 41 | 42 | // Withdraw the LP tokens from the pool. 43 | crate_token::cpi::withdraw( 44 | CpiContext::new_with_signer( 45 | self.common.crate_token_program.to_account_info(), 46 | crate_token::cpi::accounts::Withdraw { 47 | crate_token: self.common.crate_token.to_account_info(), 48 | crate_underlying: self.common.crate_collateral_tokens.to_account_info(), 49 | withdraw_authority: self.withdraw_authority.to_account_info(), 50 | withdraw_destination: self.withdraw_destination.to_account_info(), 51 | author_fee_destination: self.author_fee_destination.to_account_info(), 52 | protocol_fee_destination: self.protocol_fee_destination.to_account_info(), 53 | token_program: self.common.token_program.to_account_info(), 54 | }, 55 | WITHDRAW_AUTHORITY_SIGNER_SEEDS, 56 | ), 57 | withdraw_pool_token_amount, 58 | )?; 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl<'info> Validate<'info> for BurnCash<'info> { 65 | fn validate(&self) -> Result<()> { 66 | self.common.validate()?; 67 | assert_keys_eq!(self.burner, self.burned_cash_source.owner); 68 | assert_keys_eq!(self.burned_cash_source.mint, self.common.crate_mint); 69 | 70 | assert_keys_eq!(self.withdraw_destination.mint, self.common.collateral.mint); 71 | // author_fee_destination is validated by Crate 72 | // protocol_fee_destination is validated by Crate 73 | assert_keys_eq!(self.withdraw_authority, WITHDRAW_AUTHORITY_ADDRESS); 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /programs/brrr/src/actions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Processes actions related to the money printer 2 | 3 | use crate::BrrrCommon; 4 | use anchor_lang::prelude::*; 5 | use vipers::{assert_keys_eq, validate::Validate}; 6 | 7 | pub(crate) mod burn_cash; 8 | pub(crate) mod print_cash; 9 | 10 | impl<'info> Validate<'info> for BrrrCommon<'info> { 11 | fn validate(&self) -> Result<()> { 12 | assert_keys_eq!(self.bank, self.collateral.bank); 13 | assert_keys_eq!(self.bank.crate_mint, self.crate_mint); 14 | assert_keys_eq!(self.crate_token, self.crate_collateral_tokens.owner); 15 | assert_keys_eq!(self.crate_mint, self.crate_token.mint); 16 | assert_keys_eq!(self.crate_collateral_tokens.mint, self.collateral.mint); 17 | 18 | // saber swap 19 | self.saber_swap.validate()?; 20 | assert_keys_eq!(self.collateral.mint, self.saber_swap.arrow.mint); 21 | 22 | Ok(()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /programs/brrr/src/actions/print_cash.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::*; 4 | use anchor_lang::prelude::*; 5 | use converter::CashSwap; 6 | use vipers::{assert_keys_eq, unwrap_int, validate::Validate}; 7 | 8 | /// Prints $CASH. 9 | pub fn print_cash(ctx: Context, deposit_amount: u64) -> Result<()> { 10 | ctx.accounts.print_cash(deposit_amount) 11 | } 12 | 13 | impl<'info> PrintCash<'info> { 14 | fn print_cash(&self, deposit_amount: u64) -> Result<()> { 15 | let current_balance = self.common.crate_collateral_tokens.amount; 16 | require!( 17 | unwrap_int!(current_balance.checked_add(deposit_amount)) 18 | <= self.common.collateral.hard_cap, 19 | CollateralHardCapHit 20 | ); 21 | 22 | let swap: CashSwap = (&self.common.saber_swap).try_into()?; 23 | let print_amount = unwrap_int!(swap.calculate_cash_for_pool_tokens(deposit_amount)); 24 | if print_amount == 0 { 25 | return Ok(()); 26 | } 27 | 28 | // transfer LP tokens to the crate 29 | anchor_spl::token::transfer( 30 | CpiContext::new( 31 | self.common.token_program.to_account_info(), 32 | anchor_spl::token::Transfer { 33 | from: self.depositor_source.to_account_info(), 34 | to: self.common.crate_collateral_tokens.to_account_info(), 35 | authority: self.depositor.to_account_info(), 36 | }, 37 | ), 38 | deposit_amount, 39 | )?; 40 | 41 | // issue new crate tokens 42 | crate_token::cpi::issue( 43 | CpiContext::new_with_signer( 44 | self.common.crate_token_program.to_account_info(), 45 | crate_token::cpi::accounts::Issue { 46 | crate_token: self.common.crate_token.to_account_info(), 47 | crate_mint: self.common.crate_mint.to_account_info(), 48 | issue_authority: self.issue_authority.to_account_info(), 49 | mint_destination: self.mint_destination.to_account_info(), 50 | 51 | // there are no author/protocol fees, so we pass in garbage here 52 | author_fee_destination: self.mint_destination.to_account_info(), 53 | protocol_fee_destination: self.mint_destination.to_account_info(), 54 | 55 | token_program: self.common.token_program.to_account_info(), 56 | }, 57 | ISSUE_AUTHORITY_SIGNER_SEEDS, 58 | ), 59 | print_amount, 60 | )?; 61 | 62 | emit!(PrintCashEvent { 63 | depositor: self.depositor.key(), 64 | collateral_mint: self.common.crate_collateral_tokens.mint, 65 | deposit_amount, 66 | print_amount, 67 | timestamp: Clock::get()?.unix_timestamp 68 | }); 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | impl<'info> Validate<'info> for PrintCash<'info> { 75 | fn validate(&self) -> Result<()> { 76 | self.common.validate()?; 77 | assert_keys_eq!(self.depositor, self.depositor_source.owner); 78 | assert_keys_eq!(self.depositor_source.mint, self.common.collateral.mint); 79 | assert_keys_eq!(self.mint_destination.mint, self.common.crate_token.mint); 80 | assert_keys_eq!(self.issue_authority, ISSUE_AUTHORITY_ADDRESS); 81 | Ok(()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /programs/brrr/src/addresses.rs: -------------------------------------------------------------------------------- 1 | //! Issue authority/withdraw authority addresses. 2 | #![deny(missing_docs)] 3 | 4 | use anchor_lang::prelude::*; 5 | use anchor_lang::solana_program; 6 | 7 | /// Address of the issue authority to use for this Crate. 8 | pub static ISSUE_AUTHORITY_ADDRESS: Pubkey = 9 | static_pubkey::static_pubkey!("BJ9L3jNu6tvrUxPHTMfwyA8Lgw2X6ky5bVNyDqiXSxgA"); 10 | 11 | /// Bump seed of the above address. 12 | pub const ISSUE_AUTHORITY_ADDRESS_BUMP: u8 = 255; 13 | 14 | /// Signer seeds of the [ISSUE_AUTHORITY_ADDRESS]. 15 | pub static ISSUE_AUTHORITY_SIGNER_SEEDS: &[&[&[u8]]] = 16 | &[&[b"print", &[ISSUE_AUTHORITY_ADDRESS_BUMP]]]; 17 | 18 | /// Address of the withdraw authority to use for this Crate. 19 | pub static WITHDRAW_AUTHORITY_ADDRESS: Pubkey = 20 | static_pubkey::static_pubkey!("7Twx9JYz3gB4rF3h2cyUMnQWj9QEtmwviTvVD7xjAGEw"); 21 | 22 | /// Bump seed of the above address. 23 | pub const WITHDRAW_AUTHORITY_ADDRESS_BUMP: u8 = 255; 24 | 25 | /// Signer seeds of the [WITHDRAW_AUTHORITY_ADDRESS]. 26 | pub static WITHDRAW_AUTHORITY_SIGNER_SEEDS: &[&[&[u8]]] = 27 | &[&[b"burn", &[WITHDRAW_AUTHORITY_ADDRESS_BUMP]]]; 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | #[test] 33 | fn test_issue_authority_address() { 34 | let (key, bump) = Pubkey::find_program_address(&[b"print"], &crate::ID); 35 | assert_eq!(key, ISSUE_AUTHORITY_ADDRESS); 36 | assert_eq!(bump, ISSUE_AUTHORITY_ADDRESS_BUMP); 37 | } 38 | 39 | #[test] 40 | fn test_withdraw_authority_address() { 41 | let (key, bump) = Pubkey::find_program_address(&[b"burn"], &crate::ID); 42 | assert_eq!(key, WITHDRAW_AUTHORITY_ADDRESS); 43 | assert_eq!(bump, WITHDRAW_AUTHORITY_ADDRESS_BUMP); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /programs/brrr/src/events.rs: -------------------------------------------------------------------------------- 1 | //! Crate events 2 | #![deny(missing_docs)] 3 | 4 | use anchor_lang::prelude::*; 5 | 6 | /// Emitted when $CASH is printed. 7 | #[event] 8 | pub struct PrintCashEvent { 9 | /// The user which deposited collateral. 10 | #[index] 11 | pub depositor: Pubkey, 12 | /// The mint of the collateral used to print. 13 | #[index] 14 | pub collateral_mint: Pubkey, 15 | 16 | /// Amount of $CASH printed. 17 | pub print_amount: u64, 18 | /// Amount of collateral tokens deposited. 19 | pub deposit_amount: u64, 20 | /// Timestamp of the event. 21 | pub timestamp: i64, 22 | } 23 | 24 | /// Emitted when $CASH is burned. 25 | #[event] 26 | pub struct BurnCashEvent { 27 | /// Burner 28 | #[index] 29 | pub burner: Pubkey, 30 | /// The mint of the collateral withdrawn. 31 | #[index] 32 | pub collateral_mint: Pubkey, 33 | 34 | /// Amount of $CASH burned. 35 | pub burn_amount: u64, 36 | /// Amount of collateral tokens withdrawn. 37 | pub withdraw_amount: u64, 38 | /// Timestamp of the event. 39 | pub timestamp: i64, 40 | } 41 | -------------------------------------------------------------------------------- /programs/brrr/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Handles the printing and burning of $CASH, using 2 | //! [Saber LP](https://saber.so) [Arrows](https://arrowprotocol.com) as collateral. 3 | //! 4 | //! Printing is done in exchange for Arrow Saber LP tokens. 5 | //! Burning allows the redemption of any single Saber LP. 6 | #![deny(rustdoc::all)] 7 | #![allow(rustdoc::missing_doc_code_examples)] 8 | 9 | mod actions; 10 | mod addresses; 11 | mod events; 12 | mod saber; 13 | 14 | use anchor_lang::prelude::*; 15 | use anchor_spl::token::{Mint, Token, TokenAccount}; 16 | use arrow_sunny::Arrow; 17 | use bankman::{Bank, Collateral}; 18 | use stable_swap_anchor::SwapInfo; 19 | use vipers::validate::Validate; 20 | 21 | pub use addresses::*; 22 | pub use events::*; 23 | 24 | declare_id!("BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm"); 25 | 26 | /// [brrr] program. 27 | #[program] 28 | pub mod brrr { 29 | use super::*; 30 | 31 | /// Prints $CASH. 32 | /// 33 | /// $CASH can be printed by depositing Saber LP tokens. 34 | /// The amount of $CASH created is based on the virtual price of the 35 | /// Saber LP token; for example, if one deposits 1 USDC-USDT LP 36 | /// but that LP's virtual price is 1.02, one will receive 1.02 $CASH 37 | /// for each 1 USDC-USDT LP deposited. 38 | #[access_control(ctx.accounts.validate())] 39 | pub fn print_cash(ctx: Context, deposit_amount: u64) -> Result<()> { 40 | vipers::invariant!(false, "temporarily disabled"); 41 | actions::print_cash::print_cash(ctx, deposit_amount) 42 | } 43 | 44 | /// Burns $CASH. 45 | /// 46 | /// $CASH may be burned for any of the underlying LP tokens. 47 | /// This means that $CASHs's underlying value is the value of its cheapest ("floor") 48 | /// LP token, minus the burn fee. 49 | #[access_control(ctx.accounts.validate())] 50 | pub fn burn_cash(ctx: Context, burn_amount: u64) -> Result<()> { 51 | vipers::invariant!(false, "temporarily disabled"); 52 | actions::burn_cash::burn_cash(ctx, burn_amount) 53 | } 54 | } 55 | 56 | /// Accounts related to the Saber pool. 57 | #[derive(Accounts)] 58 | pub struct SaberSwapAccounts<'info> { 59 | /// The [Arrow] used as collateral. 60 | pub arrow: Box>, 61 | /// The Saber [SwapInfo] of the collateral. 62 | pub saber_swap: Box>, 63 | /// Mint of the pool. 64 | pub pool_mint: Box>, 65 | /// Reserve of token A. 66 | pub reserve_a: Box>, 67 | /// Reserve of token B. 68 | pub reserve_b: Box>, 69 | } 70 | 71 | /// Accounts for printing $CASH. 72 | #[derive(Accounts)] 73 | pub struct PrintCash<'info> { 74 | /// Common accounts. 75 | pub common: BrrrCommon<'info>, 76 | 77 | /// The depositor into the pool. 78 | #[account(mut)] 79 | pub depositor: Signer<'info>, 80 | 81 | /// The source of the deposited [Collateral] tokens. 82 | #[account(mut)] 83 | pub depositor_source: Box>, 84 | 85 | /// Destination of the issued $CASH. 86 | #[account(mut)] 87 | pub mint_destination: Box>, 88 | 89 | /// The [ISSUE_AUTHORITY_ADDRESS]. 90 | /// CHECK: this is handled by Vipers. 91 | pub issue_authority: UncheckedAccount<'info>, 92 | } 93 | 94 | #[derive(Accounts)] 95 | pub struct BrrrCommon<'info> { 96 | /// Information about the bank. 97 | pub bank: Box>, 98 | 99 | /// The [Collateral]. 100 | pub collateral: Box>, 101 | 102 | /// Information about the crate. 103 | pub crate_token: Box>, 104 | 105 | /// [Mint] of the [crate_token::CrateToken]. 106 | #[account(mut)] 107 | pub crate_mint: Box>, 108 | 109 | /// [TokenAccount] holding the [Collateral] tokens of the [crate_token::CrateToken]. 110 | #[account(mut)] 111 | pub crate_collateral_tokens: Box>, 112 | 113 | /// Saber swap accounts. 114 | pub saber_swap: SaberSwapAccounts<'info>, 115 | 116 | /// [Token] program. 117 | pub token_program: Program<'info, Token>, 118 | 119 | /// [crate_token::program::CrateToken] program. 120 | pub crate_token_program: Program<'info, crate_token::program::CrateToken>, 121 | } 122 | 123 | /// Accounts for burning $CASH. 124 | #[derive(Accounts)] 125 | pub struct BurnCash<'info> { 126 | /// Common accounts. 127 | pub common: BrrrCommon<'info>, 128 | 129 | /// The depositor into the pool. 130 | #[account(mut)] 131 | pub burner: Signer<'info>, 132 | 133 | /// The source of the burned $CASH. 134 | #[account(mut)] 135 | pub burned_cash_source: Box>, 136 | 137 | /// Destination of the issued tokens. 138 | #[account(mut)] 139 | pub withdraw_destination: Box>, 140 | 141 | /// Author fee token destination 142 | #[account(mut)] 143 | pub author_fee_destination: Account<'info, TokenAccount>, 144 | 145 | /// Protocol fee token destination 146 | #[account(mut)] 147 | pub protocol_fee_destination: Account<'info, TokenAccount>, 148 | 149 | /// The [WITHDRAW_AUTHORITY_ADDRESS]. 150 | /// CHECK: this is handled by Vipers. 151 | pub withdraw_authority: UncheckedAccount<'info>, 152 | } 153 | 154 | /// Errors. 155 | #[error_code] 156 | pub enum ErrorCode { 157 | #[msg("Too many of this LP token are being used as collateral.")] 158 | CollateralHardCapHit, 159 | #[msg("Insufficient pool funds.")] 160 | InsufficientFunds, 161 | } 162 | -------------------------------------------------------------------------------- /programs/brrr/src/saber.rs: -------------------------------------------------------------------------------- 1 | //! Saber adapters 2 | 3 | use std::convert::TryFrom; 4 | 5 | use anchor_lang::prelude::*; 6 | use converter::{CashSwap, SaberSwap}; 7 | use vipers::{assert_keys_eq, validate::Validate}; 8 | 9 | use crate::SaberSwapAccounts; 10 | 11 | impl<'info> TryFrom<&SaberSwapAccounts<'info>> for CashSwap { 12 | type Error = anchor_lang::error::Error; 13 | 14 | fn try_from(accounts: &SaberSwapAccounts<'info>) -> Result { 15 | Ok(Self { 16 | lp_mint_decimals: accounts.pool_mint.decimals, 17 | saber: SaberSwap { 18 | initial_amp_factor: accounts.saber_swap.initial_amp_factor, 19 | target_amp_factor: accounts.saber_swap.target_amp_factor, 20 | current_ts: Clock::get()?.unix_timestamp, 21 | start_ramp_ts: accounts.saber_swap.start_ramp_ts, 22 | stop_ramp_ts: accounts.saber_swap.stop_ramp_ts, 23 | 24 | lp_mint_supply: accounts.pool_mint.supply, 25 | token_a_reserve: accounts.reserve_a.amount, 26 | token_b_reserve: accounts.reserve_b.amount, 27 | }, 28 | }) 29 | } 30 | } 31 | 32 | impl<'info> Validate<'info> for SaberSwapAccounts<'info> { 33 | fn validate(&self) -> Result<()> { 34 | assert_keys_eq!(self.arrow.vendor_miner.mint, self.pool_mint); 35 | assert_keys_eq!(self.saber_swap.pool_mint, self.pool_mint); 36 | assert_keys_eq!(self.saber_swap.token_a.reserves, self.reserve_a); 37 | assert_keys_eq!(self.saber_swap.token_b.reserves, self.reserve_b); 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/download-programs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | cd $(dirname $0)/.. 4 | 5 | mkdir -p artifacts/programs/ 6 | 7 | # saber 8 | solana program dump SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ \ 9 | artifacts/programs/stable_swap.so --url mainnet-beta 10 | 11 | # crate 12 | curl -L https://github.com/CrateProtocol/crate/releases/download/v0.4.0/crate_token.so > \ 13 | artifacts/programs/crate_token.so 14 | 15 | # arrow 16 | curl -L https://github.com/ArrowProtocol/arrow/releases/download/v0.1.8/arrow_sunny.so > \ 17 | artifacts/programs/arrow_sunny.so 18 | 19 | # sunny 20 | solana program dump SPQR4kT3q2oUKEJes2L6NNSBCiPW9SfuhkuqC9bp6Sx \ 21 | artifacts/programs/sunny.so --url mainnet-beta 22 | 23 | # quarry 24 | curl -L https://github.com/QuarryProtocol/quarry/releases/download/v1.11.3/quarry_mine.so > \ 25 | artifacts/programs/quarry_mine.so 26 | 27 | curl -L https://github.com/QuarryProtocol/quarry/releases/download/v1.11.3/quarry_mint_wrapper.so > \ 28 | artifacts/programs/quarry_mint_wrapper.so 29 | -------------------------------------------------------------------------------- /scripts/generate-idl-types.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s extglob 4 | 5 | cd $(dirname $0)/.. 6 | 7 | generate_declaration_file() { 8 | PROGRAM_SO=$1 9 | OUT_DIR=$2 10 | 11 | prog="$(basename $PROGRAM_SO .json)" 12 | OUT_PATH="$OUT_DIR/$prog.ts" 13 | if [ ! $(which gsed) ]; then 14 | PREFIX=$(echo $prog | sed -E 's/(^|_)([a-z])/\U\2/g') 15 | else 16 | PREFIX=$(echo $prog | gsed -E 's/(^|_)([a-z])/\U\2/g') 17 | fi 18 | typename="${PREFIX}IDL" 19 | rawName="${PREFIX}JSON" 20 | 21 | # types 22 | echo "export type $typename =" >>$OUT_PATH 23 | cat $PROGRAM_SO >>$OUT_PATH 24 | echo ";" >>$OUT_PATH 25 | 26 | # raw json 27 | echo "export const $rawName: $typename =" >>$OUT_PATH 28 | cat $PROGRAM_SO >>$OUT_PATH 29 | echo ";" >>$OUT_PATH 30 | 31 | # error type 32 | echo "import { generateErrorMap } from '@saberhq/anchor-contrib';" >>$OUT_PATH 33 | echo "export const ${PREFIX}Errors = generateErrorMap($rawName);" >>$OUT_PATH 34 | } 35 | 36 | generate_sdk_idls() { 37 | SDK_DIR=${1:-"./packages/sdk/src/idls"} 38 | IDL_JSONS=$2 39 | 40 | echo "Generating IDLs for the following programs:" 41 | echo $IDL_JSONS 42 | echo "" 43 | 44 | rm -rf $SDK_DIR 45 | mkdir -p $SDK_DIR 46 | if [ $(ls -l artifacts/idl/ | wc -l) -ne 0 ]; then 47 | for f in $IDL_JSONS; do 48 | generate_declaration_file $f $SDK_DIR 49 | done 50 | if [[ $RUN_ESLINT != "none" ]]; then 51 | yarn eslint --fix $SDK_DIR 52 | fi 53 | else 54 | echo "Warning: no IDLs found. Make sure you ran ./scripts/idl.sh first." 55 | fi 56 | } 57 | 58 | generate_sdk_idls ./src/idls 'artifacts/idl/*.json' 59 | -------------------------------------------------------------------------------- /scripts/parse-idls.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script generates the IDL JSONs without buildling the full packages. 4 | 5 | rm -fr artifacts/idl/ 6 | mkdir -p artifacts/idl/ 7 | 8 | for PROGRAM in $(find programs/ -maxdepth 3 -name lib.rs); do 9 | PROGRAM_NAME=$(dirname $PROGRAM | xargs dirname | xargs basename | tr '-' '_') 10 | echo "Parsing IDL for $PROGRAM_NAME" 11 | anchor idl parse --file $PROGRAM >artifacts/idl/$PROGRAM_NAME.json || { 12 | echo "Could not parse IDL" 13 | exit 1 14 | } 15 | done 16 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | pkgs.stdenvNoCC.mkDerivation { 3 | name = "devenv"; 4 | buildInputs = with pkgs; [ 5 | (import ./ci.nix { inherit pkgs; }) 6 | rustup 7 | cargo-deps 8 | gh 9 | spl-token-cli 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /src/cashio.ts: -------------------------------------------------------------------------------- 1 | import { Arrow, generateArrowAddress } from "@arrowprotocol/arrow"; 2 | import { 3 | CRATE_ADDRESSES, 4 | CRATE_FEE_OWNER, 5 | CrateSDK, 6 | generateCrateAddress, 7 | } from "@crateprotocol/crate-sdk"; 8 | import type { AccountClient } from "@project-serum/anchor"; 9 | import { AnchorProvider, Program } from "@project-serum/anchor"; 10 | import type { AugmentedProvider, Provider } from "@saberhq/solana-contrib"; 11 | import { 12 | SignerWallet, 13 | SolanaAugmentedProvider, 14 | SolanaProvider, 15 | TransactionEnvelope, 16 | } from "@saberhq/solana-contrib"; 17 | import type { StableSwap } from "@saberhq/stableswap-sdk"; 18 | import { 19 | createInitMintInstructions, 20 | getATAAddress, 21 | getATAAddresses, 22 | getOrCreateATA, 23 | getOrCreateATAs, 24 | Token, 25 | TOKEN_PROGRAM_ID, 26 | TokenAmount, 27 | } from "@saberhq/token-utils"; 28 | import type { PublicKey, Signer } from "@solana/web3.js"; 29 | import { Keypair, SystemProgram } from "@solana/web3.js"; 30 | 31 | import { 32 | BRRR_ISSUE_AUTHORITY, 33 | BURN_WITHDRAW_AUTHORITY, 34 | CASH_DECIMALS, 35 | CASHIO_ADDRESSES, 36 | generateCollateralAddress, 37 | } from "."; 38 | import { generateBankAddress } from "./pda"; 39 | import type { BankData, BankmanIDL, BankmanProgram } from "./programs/bankman"; 40 | import { BankmanJSON } from "./programs/bankman"; 41 | import type { BrrrProgram } from "./programs/brrr"; 42 | import { BrrrJSON } from "./programs/brrr"; 43 | 44 | export interface CashioPrograms { 45 | Brrr: BrrrProgram; 46 | Bankman: BankmanProgram; 47 | } 48 | 49 | /** 50 | * Javascript SDK for interacting with Cashio. 51 | */ 52 | export class CashioSDK { 53 | /** 54 | * Reference to the Crate SDK. 55 | */ 56 | readonly crate: CrateSDK; 57 | 58 | /** 59 | * Reference to Arrow Protocol. 60 | */ 61 | readonly arrow: Arrow; 62 | 63 | constructor( 64 | readonly provider: AugmentedProvider, 65 | readonly programs: CashioPrograms 66 | ) { 67 | this.crate = CrateSDK.init(provider); 68 | this.arrow = Arrow.init(provider); 69 | } 70 | 71 | /** 72 | * Initialize from a Provider 73 | * @param provider 74 | * @returns 75 | */ 76 | static init(provider: Provider): CashioSDK { 77 | const anchorProvider = new AnchorProvider( 78 | provider.connection, 79 | provider.wallet, 80 | provider.opts 81 | ); 82 | return new CashioSDK(new SolanaAugmentedProvider(provider), { 83 | Brrr: new Program( 84 | BrrrJSON, 85 | CASHIO_ADDRESSES.Brrr, 86 | anchorProvider 87 | ) as unknown as BrrrProgram, 88 | Bankman: new Program( 89 | BankmanJSON, 90 | CASHIO_ADDRESSES.Bankman, 91 | anchorProvider 92 | ) as unknown as BankmanProgram, 93 | }); 94 | } 95 | 96 | /** 97 | * Creates a new instance of the SDK with the given keypair. 98 | */ 99 | withSigner(signer: Signer): CashioSDK { 100 | return CashioSDK.init( 101 | new SolanaProvider( 102 | this.provider.connection, 103 | this.provider.broadcaster, 104 | new SignerWallet(signer), 105 | this.provider.opts 106 | ) 107 | ); 108 | } 109 | 110 | /** 111 | * Creates a new Bank. 112 | * @returns 113 | */ 114 | async newBank({ 115 | mintKP = Keypair.generate(), 116 | admin = this.provider.wallet.publicKey, 117 | payer = this.provider.wallet.publicKey, 118 | }: { 119 | mintKP?: Keypair; 120 | admin?: PublicKey; 121 | payer?: PublicKey; 122 | } = {}): Promise<{ 123 | tx: TransactionEnvelope; 124 | bankKey: PublicKey; 125 | crateKey: PublicKey; 126 | }> { 127 | const [crateKey, crateBump] = await generateCrateAddress(mintKP.publicKey); 128 | const [bankKey, aggBump] = await generateBankAddress(crateKey); 129 | const initMintTX = await createInitMintInstructions({ 130 | provider: this.provider, 131 | mintKP, 132 | decimals: CASH_DECIMALS, 133 | mintAuthority: crateKey, 134 | freezeAuthority: crateKey, 135 | }); 136 | const newBankTX = new TransactionEnvelope(this.provider, [ 137 | this.programs.Bankman.instruction.newBank(aggBump, crateBump, { 138 | accounts: { 139 | crateMint: mintKP.publicKey, 140 | payer, 141 | bank: bankKey, 142 | crateToken: crateKey, 143 | brrrIssueAuthority: BRRR_ISSUE_AUTHORITY, 144 | burnWithdrawAuthority: BURN_WITHDRAW_AUTHORITY, 145 | admin, 146 | systemProgram: SystemProgram.programId, 147 | crateTokenProgram: CRATE_ADDRESSES.CrateToken, 148 | }, 149 | }), 150 | ]); 151 | return { tx: initMintTX.combine(newBankTX), bankKey, crateKey }; 152 | } 153 | 154 | /** 155 | * Authorizes a new pool as collateral. 156 | * @returns 157 | */ 158 | async authorizeCollateral({ 159 | bankKey, 160 | mint, 161 | curator = this.provider.wallet.publicKey, 162 | payer = this.provider.wallet.publicKey, 163 | }: { 164 | bankKey: PublicKey; 165 | mint: PublicKey; 166 | curator?: PublicKey; 167 | payer?: PublicKey; 168 | }): Promise<{ tx: TransactionEnvelope; collateralKey: PublicKey }> { 169 | const [collateralKey, bump] = await generateCollateralAddress( 170 | bankKey, 171 | mint, 172 | this.programs.Bankman.programId 173 | ); 174 | 175 | const bank: BankData | null = await ( 176 | this.programs.Bankman.account as unknown as { 177 | bank: AccountClient; 178 | } 179 | ).bank.fetchNullable(bankKey); 180 | if (!bank) { 181 | throw new Error("No bank found."); 182 | } 183 | 184 | // create the ATA for the bank 185 | const bankATA = await getOrCreateATA({ 186 | provider: this.provider, 187 | mint, 188 | owner: bankKey, 189 | }); 190 | 191 | // create the ATA for collateral 192 | const createATA = await getOrCreateATA({ 193 | provider: this.provider, 194 | mint, 195 | owner: bank.crateToken, 196 | }); 197 | 198 | const feeATA = await getOrCreateATA({ 199 | provider: this.provider, 200 | mint, 201 | owner: CRATE_FEE_OWNER, 202 | }); 203 | 204 | const newStakePoolTX = new TransactionEnvelope(this.provider, [ 205 | ...(bankATA.instruction ? [bankATA.instruction] : []), 206 | ...(createATA.instruction ? [createATA.instruction] : []), 207 | ...(feeATA.instruction ? [feeATA.instruction] : []), 208 | this.programs.Bankman.instruction.authorizeCollateral(bump, { 209 | accounts: { 210 | bank: bankKey, 211 | collateral: collateralKey, 212 | mint, 213 | curator, 214 | payer, 215 | systemProgram: SystemProgram.programId, 216 | }, 217 | }), 218 | ]); 219 | return { tx: newStakePoolTX, collateralKey }; 220 | } 221 | 222 | /** 223 | * Set collateral hard cap 224 | * @returns 225 | */ 226 | async setCollateralHardCap({ 227 | bankKey, 228 | hardCap, 229 | curator = this.provider.wallet.publicKey, 230 | }: { 231 | bankKey: PublicKey; 232 | hardCap: TokenAmount; 233 | curator?: PublicKey; 234 | }): Promise { 235 | const [collateralKey] = await generateCollateralAddress( 236 | bankKey, 237 | hardCap.token.mintAccount, 238 | this.programs.Bankman.programId 239 | ); 240 | return new TransactionEnvelope(this.provider, [ 241 | this.programs.Bankman.instruction.setCollateralHardCap(hardCap.toU64(), { 242 | accounts: { 243 | bank: bankKey, 244 | collateral: collateralKey, 245 | curator, 246 | }, 247 | }), 248 | ]); 249 | } 250 | 251 | /** 252 | * Helper for withdrawing author fees. 253 | * @returns 254 | */ 255 | async withdrawAuthorFees({ 256 | bankKey, 257 | amount, 258 | bankman = this.provider.wallet.publicKey, 259 | recipient = this.provider.wallet.publicKey, 260 | }: { 261 | bankKey: PublicKey; 262 | amount: TokenAmount; 263 | bankman?: PublicKey; 264 | recipient?: PublicKey; 265 | }): Promise { 266 | const [collateralKey] = await generateCollateralAddress( 267 | bankKey, 268 | amount.token.mintAccount, 269 | this.programs.Bankman.programId 270 | ); 271 | const authorFees = await getATAAddress({ 272 | mint: amount.token.mintAccount, 273 | owner: bankKey, 274 | }); 275 | const destination = await getOrCreateATA({ 276 | provider: this.provider, 277 | mint: amount.token.mintAccount, 278 | owner: recipient, 279 | }); 280 | return this.provider.newTX([ 281 | destination.instruction, 282 | this.programs.Bankman.instruction.withdrawAuthorFee(amount.toU64(), { 283 | accounts: { 284 | bank: bankKey, 285 | bankman, 286 | collateral: collateralKey, 287 | authorFees, 288 | destination: destination.address, 289 | tokenProgram: TOKEN_PROGRAM_ID, 290 | }, 291 | }), 292 | ]); 293 | } 294 | 295 | /** 296 | * Prints $CASH from Saber LP tokens, via Arrow. 297 | * @returns 298 | */ 299 | async printCashFromLP({ 300 | arrowMint, 301 | bankKey, 302 | lpAmount, 303 | swap, 304 | depositor = this.provider.wallet.publicKey, 305 | }: { 306 | bankKey: PublicKey; 307 | swap: StableSwap; 308 | lpAmount: TokenAmount; 309 | arrowMint: PublicKey; 310 | depositor?: PublicKey; 311 | }): Promise<{ stakeTX: TransactionEnvelope; printTX: TransactionEnvelope }> { 312 | const stakeTX = await this.arrow.stake({ 313 | arrowMint, 314 | amount: lpAmount, 315 | depositor, 316 | }); 317 | const printTX = await this.printCash({ 318 | bankKey, 319 | collateralAmount: new TokenAmount( 320 | Token.fromMint(arrowMint, lpAmount.token.decimals), 321 | lpAmount.raw 322 | ), 323 | swap, 324 | depositor, 325 | }); 326 | return { stakeTX, printTX }; 327 | } 328 | 329 | /** 330 | * Prints $CASH. 331 | * @returns 332 | */ 333 | async printCash({ 334 | bankKey, 335 | collateralAmount, 336 | swap, 337 | depositor = this.provider.wallet.publicKey, 338 | }: { 339 | bankKey: PublicKey; 340 | swap: StableSwap; 341 | collateralAmount: TokenAmount; 342 | depositor?: PublicKey; 343 | }): Promise { 344 | const bank: BankData | null = await ( 345 | this.programs.Bankman.account as unknown as { 346 | bank: AccountClient; 347 | } 348 | ).bank.fetchNullable(bankKey); 349 | if (!bank) { 350 | throw new Error("No bank found."); 351 | } 352 | 353 | const depositorATAs = await getOrCreateATAs({ 354 | provider: this.provider, 355 | mints: { 356 | collateral: collateralAmount.token.mintAccount, 357 | cash: bank.crateMint, 358 | }, 359 | owner: depositor, 360 | }); 361 | 362 | // the collateral account should never be necessary 363 | // but we don't call it here so we can create the ATA 364 | // outside of here 365 | // if (depositorATAs.createAccountInstructions.collateral) { 366 | // throw new Error("collateral ATA does not exist"); 367 | // } 368 | 369 | return new TransactionEnvelope(this.provider, [ 370 | ...(depositorATAs.createAccountInstructions.cash 371 | ? [depositorATAs.createAccountInstructions.cash] 372 | : []), 373 | this.programs.Brrr.instruction.printCash(collateralAmount.toU64(), { 374 | accounts: { 375 | common: await this._getCommonSwapAccounts({ 376 | bank: { 377 | key: bankKey, 378 | data: bank, 379 | }, 380 | swap, 381 | arrowMint: collateralAmount.token.mintAccount, 382 | }), 383 | issueAuthority: BRRR_ISSUE_AUTHORITY, 384 | depositor, 385 | depositorSource: depositorATAs.accounts.collateral, 386 | mintDestination: depositorATAs.accounts.cash, 387 | }, 388 | }), 389 | ]); 390 | } 391 | 392 | /** 393 | * Burns $CASH. 394 | * @returns 395 | */ 396 | async burnCash({ 397 | bankKey, 398 | cashAmount, 399 | swap, 400 | burner = this.provider.wallet.publicKey, 401 | arrowMint, 402 | }: { 403 | bankKey: PublicKey; 404 | swap: StableSwap; 405 | cashAmount: TokenAmount; 406 | burner?: PublicKey; 407 | arrowMint: PublicKey; 408 | }): Promise { 409 | const bank: BankData | null = await ( 410 | this.programs.Bankman.account as unknown as { 411 | bank: AccountClient; 412 | } 413 | ).bank.fetchNullable(bankKey); 414 | if (!bank) { 415 | throw new Error("No bank found."); 416 | } 417 | 418 | const bankATAs = await getATAAddresses({ 419 | mints: { 420 | withdraw: arrowMint, 421 | }, 422 | owner: bankKey, 423 | }); 424 | 425 | const burnerATAs = await getOrCreateATAs({ 426 | provider: this.provider, 427 | mints: { 428 | crate: bank.crateMint, 429 | withdraw: arrowMint, 430 | }, 431 | owner: burner, 432 | }); 433 | 434 | const protocolFeeATA = await getATAAddress({ 435 | mint: arrowMint, 436 | owner: CRATE_FEE_OWNER, 437 | }); 438 | 439 | return new TransactionEnvelope(this.provider, [ 440 | ...burnerATAs.instructions, 441 | this.programs.Brrr.instruction.burnCash(cashAmount.toU64(), { 442 | accounts: { 443 | common: await this._getCommonSwapAccounts({ 444 | bank: { 445 | key: bankKey, 446 | data: bank, 447 | }, 448 | swap, 449 | arrowMint: arrowMint, 450 | }), 451 | withdrawAuthority: BURN_WITHDRAW_AUTHORITY, 452 | burner, 453 | burnedCashSource: burnerATAs.accounts.crate, 454 | withdrawDestination: burnerATAs.accounts.withdraw, 455 | authorFeeDestination: bankATAs.accounts.withdraw.address, 456 | protocolFeeDestination: protocolFeeATA, 457 | }, 458 | }), 459 | ]); 460 | } 461 | 462 | /** 463 | * Unstakes LP tokens from an Arrow. 464 | */ 465 | async unstake({ 466 | lpAmount, 467 | arrowMint, 468 | }: { 469 | lpAmount: TokenAmount; 470 | arrowMint: PublicKey; 471 | }): Promise { 472 | return await this.arrow.unstake({ 473 | amount: lpAmount, 474 | arrowMint, 475 | }); 476 | } 477 | 478 | private async _getCommonSwapAccounts({ 479 | bank: { key: bankKey, data: bankData }, 480 | swap, 481 | arrowMint, 482 | }: { 483 | bank: { 484 | key: PublicKey; 485 | data: BankData; 486 | }; 487 | swap: StableSwap; 488 | arrowMint: PublicKey; 489 | }) { 490 | const [collateral] = await generateCollateralAddress(bankKey, arrowMint); 491 | const crateCollateralTokens = await getATAAddress({ 492 | mint: arrowMint, 493 | owner: bankData.crateToken, 494 | }); 495 | const [arrow] = await generateArrowAddress(arrowMint); 496 | 497 | return { 498 | bank: bankKey, 499 | collateral, 500 | crateCollateralTokens, 501 | crateToken: bankData.crateToken, 502 | crateMint: bankData.crateMint, 503 | saberSwap: { 504 | arrow, 505 | saberSwap: swap.config.swapAccount, 506 | poolMint: swap.state.poolTokenMint, 507 | reserveA: swap.state.tokenA.reserve, 508 | reserveB: swap.state.tokenB.reserve, 509 | }, 510 | tokenProgram: TOKEN_PROGRAM_ID, 511 | crateTokenProgram: CRATE_ADDRESSES.CrateToken, 512 | }; 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { ENV, Token } from "@saberhq/token-utils"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export const CASHIO_ADDRESSES = { 5 | Brrr: new PublicKey("BRRRot6ig147TBU6EGp7TMesmQrwu729CbG6qu2ZUHWm"), 6 | Bankman: new PublicKey("BANKhiCgEYd7QmcWwPLkqvTuuLN6qEwXDZgTe6HEbwv1"), 7 | }; 8 | 9 | export const BRRR_ISSUE_AUTHORITY = new PublicKey( 10 | "BJ9L3jNu6tvrUxPHTMfwyA8Lgw2X6ky5bVNyDqiXSxgA" 11 | ); 12 | 13 | export const BURN_WITHDRAW_AUTHORITY = new PublicKey( 14 | "7Twx9JYz3gB4rF3h2cyUMnQWj9QEtmwviTvVD7xjAGEw" 15 | ); 16 | 17 | /** 18 | * Number of decimals in $CASH. 19 | */ 20 | export const CASH_DECIMALS = 6; 21 | 22 | export const BANK_KEY = new PublicKey( 23 | "Em1PdaWY1NSpyGgKUstvZu3HzJNe9d15c3dePzBr9QwM" 24 | ); 25 | 26 | export const CRATE_TOKEN = new PublicKey( 27 | "J77Nq48nbq4Etf1voss38R3dTdR3yD7y5F6W6TaVHvmb" 28 | ); 29 | 30 | export const CRATE_MINT = new PublicKey( 31 | "CASHVDm2wsJXfhj6VWxb7GiMdoLc17Du7paH4bNr5woT" 32 | ); 33 | 34 | export const CASH_TOKEN_INFO = { 35 | name: "CASH", 36 | decimals: 6, 37 | address: CRATE_MINT.toString(), 38 | symbol: "CASH", 39 | logoURI: "/images/icon.png", 40 | extensions: { 41 | website: "https://cashio.app", 42 | }, 43 | }; 44 | 45 | export const CASH_TOKEN = new Token({ 46 | ...CASH_TOKEN_INFO, 47 | chainId: ENV.MainnetBeta, 48 | }); 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cashio"; 2 | export * from "./constants"; 3 | export * from "./parsers"; 4 | export * from "./pda"; 5 | export * from "./programs"; 6 | -------------------------------------------------------------------------------- /src/parsers.ts: -------------------------------------------------------------------------------- 1 | import { BorshCoder } from "@project-serum/anchor"; 2 | import type { KeyedAccountInfo } from "@solana/web3.js"; 3 | 4 | import type { BankData, CollateralData } from "."; 5 | import { BankmanJSON } from "./idls/bankman"; 6 | 7 | export const BANKMAN_CODER = new BorshCoder(BankmanJSON); 8 | 9 | export const parseBank = (data: KeyedAccountInfo): BankData => 10 | BANKMAN_CODER.accounts.decode("Bank", data.accountInfo.data); 11 | 12 | export const parseCollateral = (data: KeyedAccountInfo): CollateralData => 13 | BANKMAN_CODER.accounts.decode( 14 | "Collateral", 15 | data.accountInfo.data 16 | ); 17 | -------------------------------------------------------------------------------- /src/pda.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "@project-serum/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | import { CASHIO_ADDRESSES } from "."; 5 | 6 | /** 7 | * Generates the canonical Bank PDA. 8 | * 9 | * @param crateToken 10 | * @param programID 11 | * @returns 12 | */ 13 | export const generateBankAddress = ( 14 | crateToken: PublicKey, 15 | programID: PublicKey = CASHIO_ADDRESSES.Bankman 16 | ): Promise<[PublicKey, number]> => { 17 | return PublicKey.findProgramAddress( 18 | [utils.bytes.utf8.encode("Bank"), crateToken.toBuffer()], 19 | programID 20 | ); 21 | }; 22 | 23 | /** 24 | * Generates the canonical Collateral PDA. 25 | * 26 | * @param bank Bank. 27 | * @param mint Mint of the collateral. 28 | * @param programID 29 | * @returns 30 | */ 31 | export const generateCollateralAddress = ( 32 | bank: PublicKey, 33 | mint: PublicKey, 34 | programID: PublicKey = CASHIO_ADDRESSES.Bankman 35 | ): Promise<[PublicKey, number]> => { 36 | return PublicKey.findProgramAddress( 37 | [utils.bytes.utf8.encode("Collateral"), bank.toBuffer(), mint.toBuffer()], 38 | programID 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/programs/bankman.ts: -------------------------------------------------------------------------------- 1 | import type { AnchorTypes } from "@saberhq/anchor-contrib"; 2 | 3 | import type { BankmanIDL } from "../idls/bankman"; 4 | 5 | export * from "../idls/bankman"; 6 | 7 | type BankmanTypes = AnchorTypes< 8 | BankmanIDL, 9 | { 10 | bank: BankData; 11 | collateral: CollateralData; 12 | } 13 | >; 14 | 15 | export type BankData = BankmanTypes["Accounts"]["Bank"]; 16 | export type CollateralData = BankmanTypes["Accounts"]["Collateral"]; 17 | 18 | export type BankmanProgram = BankmanTypes["Program"]; 19 | 20 | export type NewBankEvent = BankmanTypes["Events"]["NewBankEvent"]; 21 | export type AddCollateralEvent = BankmanTypes["Events"]["AddCollateralEvent"]; 22 | export type SetCuratorEvent = BankmanTypes["Events"]["SetCuratorEvent"]; 23 | export type SetCollateralHardCapEvent = 24 | BankmanTypes["Events"]["SetCollateralHardCapEvent"]; 25 | -------------------------------------------------------------------------------- /src/programs/brrr.ts: -------------------------------------------------------------------------------- 1 | import type { AnchorTypes } from "@saberhq/anchor-contrib"; 2 | 3 | import type { BrrrIDL } from "../idls/brrr"; 4 | 5 | export * from "../idls/brrr"; 6 | 7 | type BrrrTypes = AnchorTypes; 8 | 9 | export type BrrrProgram = BrrrTypes["Program"]; 10 | 11 | export type BurnCashEvent = BrrrTypes["Events"]["BurnCashEvent"]; 12 | export type PrintCashEvent = BrrrTypes["Events"]["PrintCashEvent"]; 13 | -------------------------------------------------------------------------------- /src/programs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bankman"; 2 | export * from "./brrr"; 3 | -------------------------------------------------------------------------------- /tests/cashio.ts: -------------------------------------------------------------------------------- 1 | import { BN, EventParser } from "@project-serum/anchor"; 2 | import { expectTX } from "@saberhq/chai-solana"; 3 | import type { StableSwap } from "@saberhq/stableswap-sdk"; 4 | import { deployNewSwap, SWAP_PROGRAM_ID } from "@saberhq/stableswap-sdk"; 5 | import { 6 | createInitMintInstructions, 7 | createMint, 8 | getATAAddress, 9 | getTokenAccount, 10 | SPLToken, 11 | Token, 12 | TOKEN_PROGRAM_ID, 13 | TokenAmount, 14 | u64, 15 | } from "@saberhq/token-utils"; 16 | import type { PublicKey } from "@solana/web3.js"; 17 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; 18 | import { expect } from "chai"; 19 | 20 | import type { AddCollateralEvent, CashioSDK } from "../src"; 21 | import { BANKMAN_CODER } from "../src"; 22 | import { createRewarderAndQuarry } from "./quarryUtils"; 23 | import { createSunnyPool } from "./sunnyUtils"; 24 | import { makeSDK } from "./workspace"; 25 | 26 | describe("Cashio", () => { 27 | let sdk: CashioSDK; 28 | let bank: PublicKey; 29 | let cashToken: Token; 30 | 31 | beforeEach(async () => { 32 | sdk = makeSDK(); 33 | const mintKP = Keypair.generate(); 34 | const { tx: createTX, bankKey } = await sdk.newBank({ 35 | mintKP, 36 | }); 37 | bank = bankKey; 38 | cashToken = Token.fromMint(mintKP.publicKey, 6, { 39 | name: "CASH", 40 | }); 41 | await expectTX(createTX, "Create Crate Token").to.be.fulfilled; 42 | }); 43 | 44 | it("add collateral pool", async () => { 45 | const collateralKP = Keypair.generate(); 46 | const randomCollateral = await createInitMintInstructions({ 47 | provider: sdk.provider, 48 | mintKP: collateralKP, 49 | decimals: 9, 50 | }); 51 | await expectTX(randomCollateral).to.be.fulfilled; 52 | 53 | const { tx } = await sdk.authorizeCollateral({ 54 | bankKey: bank, 55 | mint: collateralKP.publicKey, 56 | }); 57 | const result = await tx.send(); 58 | await expectTX(result).to.be.fulfilled; 59 | 60 | const parser = new EventParser( 61 | sdk.programs.Bankman.programId, 62 | BANKMAN_CODER 63 | ); 64 | const logs = (await result.wait()).response.meta?.logMessages ?? []; 65 | 66 | parser.parseLogs(logs, (ev) => { 67 | const event: AddCollateralEvent = ev as AddCollateralEvent; 68 | 69 | expect(event.name).to.eq("AddCollateralEvent"); 70 | expect(event.data.bank).to.eqAddress(bank); 71 | expect(event.data.curator).to.eqAddress(sdk.provider.wallet.publicKey); 72 | expect(event.data.mint).to.eqAddress(collateralKP.publicKey); 73 | }); 74 | }); 75 | 76 | describe("with saber collateral", () => { 77 | let swap: StableSwap; 78 | let lpToken: Token; 79 | let sunnyPool: PublicKey; 80 | let arrowToken: Token; 81 | 82 | beforeEach("prepare swap", async () => { 83 | const { provider } = sdk; 84 | 85 | const adminKP = Keypair.generate(); 86 | await sdk.provider.connection.confirmTransaction( 87 | await sdk.provider.connection.requestAirdrop( 88 | adminKP.publicKey, 89 | 10 * LAMPORTS_PER_SOL 90 | ) 91 | ); 92 | const mintA = await createMint(provider, adminKP.publicKey, 6); 93 | const mintB = await createMint(provider, adminKP.publicKey, 6); 94 | 95 | const tokenA = Token.fromMint(mintA, 6); 96 | const tokenB = Token.fromMint(mintB, 6); 97 | 98 | const deployResult = await deployNewSwap({ 99 | provider, 100 | swapProgramID: SWAP_PROGRAM_ID, 101 | 102 | initialLiquidityProvider: sdk.provider.wallet.publicKey, 103 | useAssociatedAccountForInitialLP: true, 104 | tokenAMint: tokenA.mintAccount, 105 | tokenBMint: tokenB.mintAccount, 106 | adminAccount: adminKP.publicKey, 107 | ampFactor: new u64(1_000), 108 | 109 | seedPoolAccounts: ({ tokenAAccount, tokenBAccount }) => ({ 110 | instructions: [ 111 | SPLToken.createMintToInstruction( 112 | TOKEN_PROGRAM_ID, 113 | mintA, 114 | tokenAAccount, 115 | adminKP.publicKey, 116 | [], 117 | 1_000_000 118 | ), 119 | SPLToken.createMintToInstruction( 120 | TOKEN_PROGRAM_ID, 121 | mintB, 122 | tokenBAccount, 123 | adminKP.publicKey, 124 | [], 125 | 1_000_000 126 | ), 127 | ], 128 | signers: [adminKP], 129 | }), 130 | }); 131 | 132 | swap = deployResult.swap; 133 | lpToken = Token.fromMint(deployResult.swap.state.poolTokenMint, 6); 134 | 135 | // set up the quarry and rewarder 136 | const rewarderAndQuarry = await createRewarderAndQuarry({ 137 | connection: sdk.provider.connection, 138 | stakedToken: lpToken, 139 | annualRate: new u64(1_000_000_000), 140 | }); 141 | 142 | // set up the sunny pool 143 | const sunnyPoolResult = await createSunnyPool({ 144 | provider: sdk.provider, 145 | rewarder: rewarderAndQuarry.rewarder, 146 | quarry: rewarderAndQuarry.quarry, 147 | }); 148 | sunnyPool = sunnyPoolResult.sunnyPool; 149 | const internalMint = sunnyPoolResult.internalMint; 150 | 151 | // set up the sunny quarry and rewarder 152 | const sunnyRewarder = await createRewarderAndQuarry({ 153 | connection: sdk.provider.connection, 154 | stakedToken: Token.fromMint(internalMint, 6), 155 | annualRate: new u64(1_000_000_000), 156 | }); 157 | 158 | const beneficiaryKP = Keypair.generate(); 159 | const arrowMintKP = Keypair.generate(); 160 | const { initTX, newArrowTX } = await sdk.arrow.newArrow({ 161 | sunnyPool, 162 | beneficiary: beneficiaryKP.publicKey, 163 | mintKP: arrowMintKP, 164 | sunnyRewarderKey: sunnyRewarder.rewarder, 165 | }); 166 | await expectTX(initTX, "init").to.be.fulfilled; 167 | await expectTX(newArrowTX, "new arrow").to.be.fulfilled; 168 | 169 | arrowToken = Token.fromMint(arrowMintKP.publicKey, 6); 170 | 171 | const { tx } = await sdk.authorizeCollateral({ 172 | bankKey: bank, 173 | mint: arrowToken.mintAccount, 174 | }); 175 | const result = await tx.send(); 176 | await expectTX(result).to.be.fulfilled; 177 | 178 | const parser = new EventParser( 179 | sdk.programs.Bankman.programId, 180 | BANKMAN_CODER 181 | ); 182 | const logs = (await result.wait()).response.meta?.logMessages ?? []; 183 | 184 | parser.parseLogs(logs, (ev) => { 185 | const event: AddCollateralEvent = ev as AddCollateralEvent; 186 | 187 | expect(event.name).to.eq("AddCollateralEvent"); 188 | expect(event.data.bank, "bank").to.eqAddress(bank); 189 | expect(event.data.curator, "curator").to.eqAddress( 190 | sdk.provider.wallet.publicKey 191 | ); 192 | expect(event.data.mint, "mint").to.eqAddress(arrowToken.mintAccount); 193 | }); 194 | 195 | await expectTX( 196 | await sdk.setCollateralHardCap({ 197 | bankKey: bank, 198 | hardCap: new TokenAmount(arrowToken, 1_000), 199 | }), 200 | "set collateral hard cap" 201 | ).to.be.fulfilled; 202 | }); 203 | 204 | describe("print", () => { 205 | it("happy path", async () => { 206 | const { stakeTX, printTX } = await sdk.printCashFromLP({ 207 | arrowMint: arrowToken.mintAccount, 208 | bankKey: bank, 209 | lpAmount: new TokenAmount(lpToken, 1_000), 210 | swap, 211 | }); 212 | await expectTX(stakeTX, "stake").to.be.fulfilled; 213 | await expectTX(printTX, "print").to.be.fulfilled; 214 | }); 215 | 216 | it("cannot print over hard cap", async () => { 217 | const { stakeTX, printTX } = await sdk.printCashFromLP({ 218 | arrowMint: arrowToken.mintAccount, 219 | bankKey: bank, 220 | lpAmount: new TokenAmount(lpToken, 1_001), 221 | swap, 222 | }); 223 | await expectTX(stakeTX, "stake").to.be.fulfilled; 224 | await expectTX(printTX, "print over hard cap").to.be.rejected; 225 | }); 226 | }); 227 | 228 | it("burn", async () => { 229 | const { stakeTX, printTX } = await sdk.printCashFromLP({ 230 | arrowMint: arrowToken.mintAccount, 231 | bankKey: bank, 232 | lpAmount: new TokenAmount(lpToken, 1_000), 233 | swap, 234 | }); 235 | await expectTX(stakeTX, "stake").to.be.fulfilled; 236 | await expectTX(printTX, "print").to.be.fulfilled; 237 | 238 | expect( 239 | ( 240 | await getTokenAccount( 241 | sdk.provider, 242 | await getATAAddress({ 243 | mint: cashToken.mintAccount, 244 | owner: sdk.provider.wallet.publicKey, 245 | }) 246 | ) 247 | ).amount, 248 | "cash in wallet" 249 | ).to.bignumber.eq(new BN(1_000)); 250 | 251 | const burnTX = await sdk.burnCash({ 252 | arrowMint: arrowToken.mintAccount, 253 | bankKey: bank, 254 | cashAmount: new TokenAmount(cashToken, 1_000), 255 | swap, 256 | }); 257 | await expectTX(burnTX, "burn").to.be.fulfilled; 258 | 259 | expect( 260 | ( 261 | await getTokenAccount( 262 | sdk.provider, 263 | await getATAAddress({ 264 | mint: cashToken.mintAccount, 265 | owner: sdk.provider.wallet.publicKey, 266 | }) 267 | ) 268 | ).amount, 269 | "no more tokens" 270 | ).to.bignumber.eq("0"); 271 | }); 272 | }); 273 | }); 274 | -------------------------------------------------------------------------------- /tests/fixture-key.json: -------------------------------------------------------------------------------- 1 | [ 2 | 148, 243, 45, 212, 135, 246, 72, 26, 73, 140, 56, 126, 177, 224, 224, 170, 3 | 236, 120, 106, 35, 82, 69, 249, 235, 237, 125, 11, 106, 191, 236, 0, 39, 4, 4 | 105, 253, 195, 52, 130, 7, 89, 145, 103, 113, 215, 175, 175, 188, 81, 200, 89, 5 | 116, 202, 162, 197, 253, 236, 129, 122, 206, 63, 253, 182, 227, 207 6 | ] 7 | -------------------------------------------------------------------------------- /tests/quarryUtils.ts: -------------------------------------------------------------------------------- 1 | // stolen from https://github.com/QuarryProtocol/quarry/blob/master/tests/quarryUtils.ts 2 | import type { RewarderWrapper } from "@quarryprotocol/quarry-sdk"; 3 | import { QuarrySDK, QuarryWrapper } from "@quarryprotocol/quarry-sdk"; 4 | import { expectTX } from "@saberhq/chai-solana"; 5 | import { SignerWallet, SolanaProvider } from "@saberhq/solana-contrib"; 6 | import { Token, u64 } from "@saberhq/token-utils"; 7 | import type { Connection, PublicKey } from "@solana/web3.js"; 8 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; 9 | 10 | export const createRewarderAndQuarry = async ({ 11 | connection, 12 | stakedToken, 13 | annualRate, 14 | adminKP = Keypair.generate(), 15 | }: { 16 | adminKP?: Keypair; 17 | connection: Connection; 18 | /** 19 | * Token to stake in the Quarry. 20 | */ 21 | stakedToken: Token; 22 | annualRate: u64; 23 | }): Promise<{ 24 | adminKP: Keypair; 25 | /** 26 | * Token issued as Quarry rewards. 27 | */ 28 | rewardsToken: Token; 29 | 30 | quarry: PublicKey; 31 | /** 32 | * Quarry wrapper 33 | */ 34 | quarryW: QuarryWrapper; 35 | 36 | rewarder: PublicKey; 37 | /** 38 | * Rewarder wrapper 39 | */ 40 | rewarderW: RewarderWrapper; 41 | }> => { 42 | const { rewardsToken, rewarder, rewarderW, quarrySDK } = await createRewarder( 43 | { 44 | connection, 45 | adminKP, 46 | } 47 | ); 48 | 49 | const { tx: createQuarryTX, quarry: quarryKey } = 50 | await rewarderW.createQuarry({ 51 | token: stakedToken, 52 | }); 53 | await expectTX(createQuarryTX, "Create quarry").to.be.fulfilled; 54 | 55 | const quarryW = await QuarryWrapper.load({ 56 | sdk: quarrySDK, 57 | token: stakedToken, 58 | key: quarryKey, 59 | }); 60 | const setShareTX = quarryW.setRewardsShare(new u64(1)); 61 | const syncRewardsTX = await rewarderW.setAndSyncAnnualRewards(annualRate, [ 62 | stakedToken.mintAccount, 63 | ]); 64 | await expectTX(setShareTX.combine(syncRewardsTX), "set rewards").to.be 65 | .fulfilled; 66 | 67 | return { 68 | adminKP, 69 | rewardsToken, 70 | quarry: quarryKey, 71 | quarryW, 72 | rewarder, 73 | rewarderW, 74 | }; 75 | }; 76 | 77 | export const createRewarder = async ({ 78 | connection, 79 | adminKP = Keypair.generate(), 80 | }: { 81 | adminKP?: Keypair; 82 | connection: Connection; 83 | }): Promise<{ 84 | quarrySDK: QuarrySDK; 85 | adminKP: Keypair; 86 | /** 87 | * Token issued as Quarry rewards. 88 | */ 89 | rewardsToken: Token; 90 | rewarder: PublicKey; 91 | rewarderW: RewarderWrapper; 92 | }> => { 93 | await connection.confirmTransaction( 94 | await connection.requestAirdrop(adminKP.publicKey, 10 * LAMPORTS_PER_SOL) 95 | ); 96 | 97 | const primaryQuarrySDK = QuarrySDK.load({ 98 | provider: SolanaProvider.load({ 99 | connection, 100 | sendConnection: connection, 101 | wallet: new SignerWallet(adminKP), 102 | }), 103 | }); 104 | 105 | const primaryMintWrapper = 106 | await primaryQuarrySDK.mintWrapper.newWrapperAndMint({ 107 | // 1B 108 | hardcap: new u64("1000000000000000"), 109 | decimals: 6, 110 | }); 111 | await expectTX(primaryMintWrapper.tx, "primary mint wrapper").to.be.fulfilled; 112 | const primaryRewarder = await primaryQuarrySDK.mine.createRewarder({ 113 | mintWrapper: primaryMintWrapper.mintWrapper, 114 | }); 115 | await expectTX(primaryRewarder.tx, "primary rewarder").to.be.fulfilled; 116 | 117 | // create minter info 118 | const minterAddTX = await primaryQuarrySDK.mintWrapper.newMinterWithAllowance( 119 | primaryMintWrapper.mintWrapper, 120 | primaryRewarder.key, 121 | new u64(1_000_000000) 122 | ); 123 | await expectTX(minterAddTX, "Minter add").to.be.fulfilled; 124 | 125 | // create quarry 126 | const rewarderW = await primaryQuarrySDK.mine.loadRewarderWrapper( 127 | primaryRewarder.key 128 | ); 129 | 130 | return { 131 | quarrySDK: primaryQuarrySDK, 132 | adminKP, 133 | rewardsToken: Token.fromMint(primaryMintWrapper.mint, 6), 134 | rewarder: rewarderW.rewarderKey, 135 | rewarderW, 136 | }; 137 | }; 138 | -------------------------------------------------------------------------------- /tests/sunnyUtils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | generateSunnyPoolAddress, 3 | SUNNY_CREATOR_KEY, 4 | SUNNY_PROGRAM, 5 | SunnyPoolQuarryJSON, 6 | } from "@arrowprotocol/arrow"; 7 | import { Program } from "@project-serum/anchor"; 8 | import { expectTX } from "@saberhq/chai-solana"; 9 | import type { Provider } from "@saberhq/solana-contrib"; 10 | import { TransactionEnvelope } from "@saberhq/solana-contrib"; 11 | import { createInitMintInstructions } from "@saberhq/token-utils"; 12 | import type { PublicKey } from "@solana/web3.js"; 13 | import { Keypair, SystemProgram } from "@solana/web3.js"; 14 | 15 | export const createSunnyPool = async ({ 16 | provider, 17 | rewarder, 18 | quarry, 19 | }: { 20 | provider: Provider; 21 | rewarder: PublicKey; 22 | quarry: PublicKey; 23 | }): Promise<{ sunnyPool: PublicKey; internalMint: PublicKey }> => { 24 | const sunny = new Program(SunnyPoolQuarryJSON, SUNNY_PROGRAM); 25 | // set up the sunny pool 26 | const [pool, bump] = await generateSunnyPoolAddress({ 27 | quarry, 28 | }); 29 | const sunnyMintKP = Keypair.generate(); 30 | const createInternalMint = await createInitMintInstructions({ 31 | provider, 32 | mintKP: sunnyMintKP, 33 | decimals: 6, 34 | mintAuthority: pool, 35 | freezeAuthority: pool, 36 | }); 37 | const newPoolIx = sunny.instruction.newPool(bump, { 38 | accounts: { 39 | creator: SUNNY_CREATOR_KEY, 40 | rewarder, 41 | quarry, 42 | pool, 43 | payer: provider.wallet.publicKey, 44 | systemProgram: SystemProgram.programId, 45 | internalMint: sunnyMintKP.publicKey, 46 | }, 47 | }); 48 | await expectTX( 49 | createInternalMint.combine(new TransactionEnvelope(provider, [newPoolIx])), 50 | "create sunny pool" 51 | ).to.be.fulfilled; 52 | return { sunnyPool: pool, internalMint: sunnyMintKP.publicKey }; 53 | }; 54 | -------------------------------------------------------------------------------- /tests/workspace.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { AnchorProvider } from "@project-serum/anchor"; 3 | import { chaiSolana } from "@saberhq/chai-solana"; 4 | import { SolanaProvider } from "@saberhq/solana-contrib"; 5 | import chai from "chai"; 6 | 7 | import { CashioSDK } from "../src"; 8 | 9 | chai.use(chaiSolana); 10 | 11 | const anchorProvider = AnchorProvider.env(); 12 | anchor.setProvider(anchorProvider); 13 | 14 | const provider = SolanaProvider.init({ 15 | connection: anchorProvider.connection, 16 | wallet: anchorProvider.wallet, 17 | opts: anchorProvider.opts, 18 | }); 19 | 20 | export const makeSDK = (): CashioSDK => { 21 | return CashioSDK.init(provider); 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": [], 5 | "noEmit": false, 6 | "outDir": "dist/cjs/" 7 | }, 8 | "include": ["src/"], 9 | "exclude": ["**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "outDir": "dist/esm/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@saberhq/tsconfig/tsconfig.lib.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "noEmit": true, 6 | "types": ["mocha"] 7 | }, 8 | "include": ["src/", "tests/"] 9 | } 10 | --------------------------------------------------------------------------------