├── .github └── workflows │ └── build-all.yml ├── .gitignore ├── 0-quickstart_transfer ├── README.md ├── main.ts ├── package.json ├── tsconfig.json └── yarn.lock ├── 1-spl_transfer ├── README.md ├── package.json ├── tests │ ├── main.ts │ └── utils.ts ├── tsconfig.json └── yarn.lock ├── 2-counter ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── migrations │ └── deploy.ts ├── new-program-id.sh ├── package.json ├── programs │ └── counter │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ └── lib.rs ├── tests │ └── counter.ts ├── tsconfig.json └── yarn.lock ├── README.md ├── distributor ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── distributor │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── create.rs │ │ ├── distribute.rs │ │ ├── mod.rs │ │ └── update.rs │ │ ├── lib.rs │ │ └── state │ │ ├── distributor.rs │ │ └── mod.rs ├── tests │ └── distributor.ts ├── tsconfig.json └── yarn.lock ├── event_stream ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── event_stream │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── initialize.rs │ │ ├── mod.rs │ │ ├── ping.rs │ │ └── process_event.rs │ │ ├── lib.rs │ │ └── state │ │ ├── authority.rs │ │ ├── event.rs │ │ └── mod.rs ├── tests │ └── event_stream.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock ├── hello_clockwork ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── migrations │ └── deploy.ts ├── new-program-id.sh ├── package.json ├── programs │ └── hello_clockwork │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ └── lib.rs ├── tests │ └── hello_clockwork.ts ├── tsconfig.json └── yarn.lock ├── openbook_crank ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── client │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── utils.rs ├── clockwork_localnet.sh ├── dex │ ├── serum_dex-keypair.json │ └── serum_dex.so ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── openbook_crank │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── consume_events.rs │ │ ├── delete.rs │ │ ├── initialize.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ └── state │ │ ├── crank.rs │ │ ├── mod.rs │ │ └── openbook_dex.rs ├── tests │ └── openbook_crank.ts ├── tsconfig.json └── yarn.lock ├── openbook_dca ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── client │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── utils.rs ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── openbook_dca │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── dca_create.rs │ │ ├── dca_delete.rs │ │ ├── dca_update.rs │ │ ├── mod.rs │ │ └── swap.rs │ │ ├── lib.rs │ │ └── state │ │ ├── dca.rs │ │ ├── mod.rs │ │ └── openbook_dex.rs ├── tests │ └── openbook_dca.ts ├── tsconfig.json └── yarn.lock ├── orca_dca ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── client │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── utils.rs ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── orca_dca │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── dca_create.rs │ │ ├── dca_delete.rs │ │ ├── dca_update.rs │ │ ├── mod.rs │ │ └── proxy_swap.rs │ │ ├── lib.rs │ │ └── state │ │ ├── dca.rs │ │ └── mod.rs ├── tests │ └── orca_dca.ts ├── tsconfig.json └── yarn.lock ├── orca_whirlpool_dca ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── client │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── utils.rs ├── crates │ └── whirlpool │ │ ├── Cargo.toml │ │ ├── idl.json │ │ └── src │ │ ├── lib.rs │ │ └── utils.rs ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── orca_whirlpool_dca │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── dca_create.rs │ │ ├── dca_delete.rs │ │ ├── dca_update.rs │ │ ├── get_tick_arrays.rs │ │ ├── mod.rs │ │ └── swap.rs │ │ ├── lib.rs │ │ └── state │ │ ├── dca.rs │ │ └── mod.rs ├── tests │ └── orca_whirlpool_dca.ts ├── tsconfig.json └── yarn.lock ├── payments ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── payments │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── create_payment.rs │ │ ├── disburse_payment.rs │ │ ├── mod.rs │ │ └── update_payment.rs │ │ ├── lib.rs │ │ └── state │ │ ├── mod.rs │ │ └── payment.rs ├── tests │ └── payments.ts ├── tsconfig.json └── yarn.lock ├── pyth-trigger-test ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── pyth-trigger-test │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ └── lib.rs ├── tests │ └── pyth-trigger-test.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock ├── pyth_feed ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── client │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── pyth_feed │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── create_feed.rs │ │ ├── mod.rs │ │ └── process_feed.rs │ │ ├── lib.rs │ │ └── state │ │ ├── feed.rs │ │ └── mod.rs ├── tests │ └── pyth_feed.ts ├── tsconfig.json └── yarn.lock ├── pyth_stats ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── client │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── pyth_stats │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── id.rs │ │ ├── instructions │ │ ├── calc.rs │ │ ├── initialize.rs │ │ ├── mod.rs │ │ └── realloc_buffers.rs │ │ ├── lib.rs │ │ └── state │ │ ├── buffers.rs │ │ ├── mod.rs │ │ └── stat.rs ├── tests │ └── pyth_stats.ts ├── tsconfig.json └── yarn.lock ├── scripts ├── build-all.sh ├── deploy.sh └── update-clockwork-dependencies.sh ├── spl_transfer ├── README.md ├── package.json ├── tests │ ├── main.ts │ └── utils.ts ├── tsconfig.json └── yarn.lock ├── subscriptions ├── .env.example ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── client │ ├── Cargo.toml │ └── src │ │ ├── commands │ │ ├── create_mint.rs │ │ ├── create_subscriber.rs │ │ ├── create_subscription.rs │ │ ├── deactivate_subscription.rs │ │ ├── mod.rs │ │ ├── subscribe.rs │ │ ├── unsubscribe.rs │ │ ├── update_authority.rs │ │ └── withdraw.rs │ │ ├── main.rs │ │ └── utils.rs ├── deploy.sh ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── subscriptions │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── contexts │ │ ├── create_subscriber.rs │ │ ├── create_subscription.rs │ │ ├── deactivate_subscription.rs │ │ ├── disburse_payment.rs │ │ ├── mod.rs │ │ ├── subscribe.rs │ │ ├── unsubscribe.rs │ │ ├── update_authority.rs │ │ └── withdraw.rs │ │ ├── error.rs │ │ ├── id.rs │ │ ├── lib.rs │ │ └── state │ │ ├── mod.rs │ │ ├── subscriber.rs │ │ └── subscription.rs ├── tests │ └── subscriptions.ts ├── tsconfig.json └── yarn.lock └── utils ├── dist ├── index.d.ts └── index.js ├── lib └── index.ts ├── package-lock.json ├── package.json └── tsconfig.json /.github/workflows/build-all.yml: -------------------------------------------------------------------------------- 1 | name: Build-All 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build 18 | run: ./scripts/build-all.sh 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | dist 8 | -------------------------------------------------------------------------------- /0-quickstart_transfer/README.md: -------------------------------------------------------------------------------- 1 | # **Sol Transfer** 2 | 3 | This example shows how to schedule a thread to transfer sol every 10s using the typescript SDK only. 4 | For a complete guide to this example project, please see to the [Clockwork docs](https://docs.clockwork.xyz/welcome/quickstart) 5 | 6 | --- 7 | 8 | Testing locally: 9 | ```bash 10 | cargo install -f --locked clockwork-cli 11 | clockwork localnet 12 | ``` 13 | 14 | Run the tests! 15 | ```bash 16 | yarn 17 | ``` 18 | 19 | ```bash 20 | yarn test 21 | ``` 22 | -------------------------------------------------------------------------------- /0-quickstart_transfer/main.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { 3 | Connection, 4 | Keypair, 5 | LAMPORTS_PER_SOL, 6 | SystemProgram, 7 | Transaction, 8 | } from "@solana/web3.js"; 9 | import * as anchor from "@coral-xyz/anchor"; 10 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 11 | import { ClockworkProvider, PAYER_PUBKEY } from "@clockwork-xyz/sdk"; 12 | 13 | const connection = new Connection("http://localhost:8899", "processed"); 14 | const payer = Keypair.fromSecretKey( 15 | Buffer.from(JSON.parse(require("fs").readFileSync( 16 | require("os").homedir() + "/.config/solana/id.json", 17 | "utf-8" 18 | ))) 19 | ); 20 | 21 | // Prepare clockworkProvider 22 | const provider = new anchor.AnchorProvider( 23 | connection, 24 | new NodeWallet(payer), 25 | anchor.AnchorProvider.defaultOptions() 26 | ); 27 | const clockworkProvider = ClockworkProvider.fromAnchorProvider(provider); 28 | 29 | 30 | describe("transfer", async () => { 31 | it("Transfers SOL every 10 seconds", async () => { 32 | const threadId = "sol_transferjs" + new Date().getTime(); 33 | const [threadAddress] = clockworkProvider.getThreadPDA( 34 | payer.publicKey, // authority 35 | threadId 36 | ) 37 | 38 | const recipient = Keypair.generate().publicKey; 39 | console.log(`🫴 recipient: ${recipient.toString()}\n`); 40 | 41 | // 1️⃣ Prepare an instruction to be automated. 42 | const transferIx = SystemProgram.transfer({ 43 | fromPubkey: PAYER_PUBKEY, 44 | toPubkey: recipient, 45 | lamports: LAMPORTS_PER_SOL, 46 | }); 47 | 48 | // 2️⃣ Define a trigger condition. 49 | const trigger = { 50 | cron: { 51 | schedule: "*/10 * * * * * *", 52 | skippable: true, 53 | }, 54 | }; 55 | 56 | // 3️⃣ Create the thread. 57 | const ix = await clockworkProvider.threadCreate( 58 | payer.publicKey, // authority 59 | threadId, // id 60 | [transferIx], // instructions 61 | trigger, // trigger 62 | 50 * LAMPORTS_PER_SOL, // amount to fund the thread with 63 | ); 64 | const tx = new Transaction().add(ix); 65 | const signature = await clockworkProvider.anchorProvider.sendAndConfirm(tx); 66 | console.log(`🗺️ explorer: https://app.clockwork.xyz/threads/${threadAddress}?cluster=custom&customUrl=${connection.rpcEndpoint}\n`); 67 | 68 | // Check balance of recipient address 69 | await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); 70 | let balance = await connection.getBalance(recipient) / LAMPORTS_PER_SOL; 71 | console.log(`✅ recipient balance: ${balance} SOL\n`); 72 | expect(balance).to.eq(1); 73 | 74 | await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); 75 | balance = await connection.getBalance(recipient) / LAMPORTS_PER_SOL; 76 | console.log(`✅ recipient balance: ${balance} SOL\n`); 77 | expect(balance).to.eq(2); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /0-quickstart_transfer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "yarn run ts-mocha -p tsconfig.json -t 1000000 main.ts" 4 | }, 5 | "dependencies": { 6 | "@clockwork-xyz/sdk": "^0.3.4", 7 | "@coral-xyz/anchor": "^0.27.0", 8 | "@solana/web3.js": "^1.73.0" 9 | }, 10 | "devDependencies": { 11 | "@types/chai": "^4.3.3", 12 | "@types/mocha": "^9.1.1", 13 | "chai": "^4.3.6", 14 | "mocha": "^10.0.0", 15 | "ts-mocha": "^10.0.0", 16 | "typescript": "^4.8.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /0-quickstart_transfer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015"], 4 | "module": "commonjs", 5 | "target": "es6", 6 | "esModuleInterop": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /1-spl_transfer/README.md: -------------------------------------------------------------------------------- 1 | # **SPL Transfer** 2 | 3 | This example shows how to schedule a thread to transfer spl tokens every 10s using the typescript SDK only. 4 | For a complete guide to this example project, please see to the [Clockwork docs](https://docs.clockwork.xyz/guides/1-spl-transfer). 5 | 6 | --- 7 | 8 | Testing locally: 9 | ```bash 10 | cargo install -f --locked clockwork-cli 11 | clockwork localnet 12 | ``` 13 | 14 | Run the tests! 15 | ```bash 16 | yarn 17 | ``` 18 | 19 | ```bash 20 | yarn test 21 | ``` 22 | -------------------------------------------------------------------------------- /1-spl_transfer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spl-transfer", 3 | "version": "1.0.0", 4 | "description": "SPL Transfer Example", 5 | "scripts": { 6 | "test": "yarn run ts-mocha -p tsconfig.json -t 1000000 ./tests/main.ts" 7 | }, 8 | "dependencies": { 9 | "@clockwork-xyz/sdk": "^0.3.4", 10 | "@coral-xyz/anchor": "^0.27.0", 11 | "@solana/spl-token": "^0.3.6", 12 | "@solana/web3.js": "^1.73.0" 13 | }, 14 | "devDependencies": { 15 | "@types/chai": "^4.3.3", 16 | "@types/mocha": "^9.1.1", 17 | "chai": "^4.3.6", 18 | "mocha": "^10.0.0", 19 | "ts-mocha": "^10.0.0", 20 | "typescript": "^4.8.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /1-spl_transfer/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from "@solana/web3.js"; 2 | import { ClockworkProvider } from "@clockwork-xyz/sdk"; 3 | 4 | const keypairFromFile = (path: string): Keypair => { 5 | return Keypair.fromSecretKey( 6 | Buffer.from(JSON.parse(require("fs").readFileSync(path, "utf-8"))) 7 | ); 8 | }; 9 | 10 | // helpers 11 | let lastThreadExec = BigInt(0); 12 | const waitForThreadExec = async ( 13 | clockworkProvider: ClockworkProvider, 14 | thread: PublicKey, 15 | maxWait: number = 60 16 | ) => { 17 | let i = 1; 18 | while (true) { 19 | const execContext = (await clockworkProvider.getThreadAccount(thread)) 20 | .execContext; 21 | if (execContext) { 22 | if ( 23 | lastThreadExec.toString() == "0" || 24 | execContext.lastExecAt > lastThreadExec 25 | ) { 26 | lastThreadExec = execContext.lastExecAt; 27 | break; 28 | } 29 | } 30 | if (i == maxWait) throw Error("Timeout"); 31 | i += 1; 32 | await new Promise((r) => setTimeout(r, i * 1000)); 33 | } 34 | }; 35 | 36 | export { keypairFromFile, waitForThreadExec }; 37 | -------------------------------------------------------------------------------- /1-spl_transfer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015"], 4 | "module": "commonjs", 5 | "target": "es6", 6 | "esModuleInterop": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /2-counter/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /2-counter/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /2-counter/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | counter = "5uEViA2t53qXNmXq4pTkaA8MZuDzxNKsRRDnZj7nPqNg" 7 | [programs.devnet] 8 | counter = "5uEViA2t53qXNmXq4pTkaA8MZuDzxNKsRRDnZj7nPqNg" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "devnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /2-counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /2-counter/README.md: -------------------------------------------------------------------------------- 1 | # **1 - Counter** 2 | 3 | For a complete guide to this example project, please see to the [Clockwork docs](https://docs.clockwork.xyz/developers/guides/1-counter). 4 | 5 | --- 6 | 7 | Testing locally: 8 | ```bash 9 | cargo install -f --locked clockwork-cli 10 | clockwork localnet 11 | ``` 12 | 13 | Get a new program id: 14 | ```bash 15 | ./new-program-id.sh 16 | ``` 17 | 18 | Run the tests and observe the logs: 19 | ```bash 20 | anchor test --skip-local-validator 21 | ``` 22 | -------------------------------------------------------------------------------- /2-counter/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /2-counter/new-program-id.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /2-counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.26.0", 8 | "@clockwork-xyz/sdk": "^0.3.0" 9 | }, 10 | "devDependencies": { 11 | "@utils": "file:../utils", 12 | "chai": "^4.3.4", 13 | "mocha": "^9.0.3", 14 | "ts-mocha": "^10.0.0", 15 | "@types/bn.js": "^5.1.0", 16 | "@types/chai": "^4.3.0", 17 | "@types/mocha": "^9.0.0", 18 | "typescript": "^4.3.5", 19 | "prettier": "^2.6.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /2-counter/programs/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "counter" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.27.0", features = ["init-if-needed"] } 20 | clockwork-sdk = { version = "2.0.15" } 21 | -------------------------------------------------------------------------------- /2-counter/programs/counter/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /2-counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Clockwork examples** 2 | 3 | This repo contains example automated smart-contracts built with the [**Clockwork SDK**](https://docs.clockwork.xyz). 4 | - [**0. Quickstart Sol Transfer (Typescript)**](https://github.com/clockwork-xyz/examples/tree/main/0-quickstart_transfer) – Executes an SOL transfer on a user-defined schedule. 5 | - [**1. SPL Transfer (Typescript)**](https://github.com/clockwork-xyz/examples/tree/main/1-spl_transfer) – Executes an SPL token transfer on a user-defined schedule. 6 | - [**2. Counter**](https://github.com/clockwork-xyz/examples/tree/main/2-counter) – Creates a thread via CPI that increments a counter every 10 seconds 7 | - [**Recurring payments**](https://github.com/clockwork-xyz/examples/tree/main/payments) – Executes an SPL token transfer on a user-defined schedule. 8 | - [**Token distributor**](https://github.com/clockwork-xyz/examples/tree/main/distributor) – Mints a new token and sends it to a target user every 60 seconds. 9 | - [**Dollar cost averaging**](https://github.com/clockwork-xyz/examples/tree/main/investments) – Executes a swap on Serum on a user-defined schedule. 10 | - [**Serum crank**](https://github.com/clockwork-xyz/examples/tree/main/serum_crank) – Indefinitely processes open orders on a permissioned Serum market. 11 | - [**Subscriptions**](https://github.com/clockwork-xyz/examples/tree/main/subscriptions) – Allow users to subscribe to subscriptions by paying on a recurrent schedule. 12 | 13 | -------------------------------------------------------------------------------- /distributor/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /distributor/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /distributor/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | 4 | [programs.localnet] 5 | distributor = "ARdMXW5cuNAoNmtCDfrJRYStNXsHgc6yeapTkz9YAYSq" 6 | 7 | [programs.devnet] 8 | distributor = "ARdMXW5cuNAoNmtCDfrJRYStNXsHgc6yeapTkz9YAYSq" 9 | 10 | [registry] 11 | url = "https://anchor.projectserum.com" 12 | 13 | [provider] 14 | cluster = "devnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /distributor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | -------------------------------------------------------------------------------- /distributor/README.md: -------------------------------------------------------------------------------- 1 | # **Distributor Program (Token Minter)** 2 | 3 | This program creates a Thread that stream token mints to a recipient. 4 | - First, we create a distributor account and transfer the token mint authority to the distributor account. 5 | - Then, we schedule a Thread to mint tokens using the distributor account's information. 6 | 7 | --- 8 | 9 | ## Workflow 10 | **0. Install and run the Clockwork CLI** 11 | ```bash 12 | cargo install -f --locked clockwork-cli 13 | clockwork localnet 14 | ``` 15 | 16 | **1. Program Side - Deploying the Handler Program** 17 | We start by defining an instruction to execute, that is the __"WHAT"__: 18 | - We have already prepared a handler. 19 | - You just have to deploy it using `./deploy.sh localnet` _(nothing fancy, the usual program ids and network switch)_. 20 | 21 | **2. Client Side - Creating a Thread** 22 | Time to switch perspective, we need to do some work off-chain now, we will create a Thread, that's the __"HOW"__: 23 | - Check the `tests` folder, we are using anchor tests as a client. 24 | - Run `anchor test --skip-local-validator`, this will create a __Thread__ that listens for a certain account and print logs whenever the 25 | account is updated. 26 | 27 | ## How do I know if it works? 28 | Let's see how we can observe our newly created Thread: 29 | - Run the tests! 30 | ```bash 31 | anchor test --skip-local-validator 32 | ``` 33 | - If you have the Clockwork Cli installed, you can use the `clockwork` command 34 | ```bash 35 | clockwork thread get --address 36 | clockwork thread get 37 | ``` 38 | 39 | --- 40 | 41 | ## Common Errors 42 | Please refer to the [FAQ](https://github.com/clockwork-xyz/docs/blob/main/FAQ.md#common-errors). 43 | 44 | ## Questions 45 | Come build with us and ask us questions [Discord](https://discord.gg/epHsTsnUre)! 46 | -------------------------------------------------------------------------------- /distributor/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /distributor/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /distributor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@clockwork-xyz/sdk": "^0.2.3", 8 | "@solana/spl-token": "^0.2.0", 9 | "@project-serum/anchor": "^0.26.0" 10 | }, 11 | "devDependencies": { 12 | "@types/bn.js": "^5.1.0", 13 | "@types/chai": "^4.3.0", 14 | "@types/mocha": "^9.0.0", 15 | "chai": "^4.3.4", 16 | "mocha": "^9.0.3", 17 | "prettier": "^2.6.2", 18 | "ts-mocha": "^10.0.0", 19 | "typescript": "^4.3.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /distributor/programs/distributor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "distributor" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "distributor" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [profile.release] 19 | overflow-checks = true 20 | 21 | [dependencies] 22 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 23 | anchor-spl = { version = "0.26.0", features = ["token"] } 24 | clockwork-sdk = { version = "~2.0.1" } 25 | -------------------------------------------------------------------------------- /distributor/programs/distributor/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("ARdMXW5cuNAoNmtCDfrJRYStNXsHgc6yeapTkz9YAYSq"); 4 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/instructions/create.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{ 4 | prelude::*, 5 | solana_program::{system_program, sysvar}, 6 | }, 7 | anchor_spl::{ 8 | associated_token::AssociatedToken, 9 | token::{self, spl_token::instruction::AuthorityType, Mint, SetAuthority, TokenAccount}, 10 | }, 11 | std::mem::size_of, 12 | }; 13 | 14 | #[derive(Accounts)] 15 | #[instruction(mint_amount: u64)] 16 | pub struct Create<'info> { 17 | #[account(mut)] 18 | pub authority: Signer<'info>, 19 | 20 | #[account(address = anchor_spl::associated_token::ID)] 21 | pub associated_token_program: Program<'info, AssociatedToken>, 22 | 23 | #[account(mut)] 24 | pub mint: Account<'info, Mint>, 25 | 26 | #[account( 27 | init, 28 | seeds = [SEED_DISTRIBUTOR, mint.key().as_ref(), authority.key().as_ref()], 29 | bump, 30 | payer = authority, 31 | space = 8 + size_of::(), 32 | )] 33 | pub distributor: Account<'info, Distributor>, 34 | 35 | /// CHECK: manually validated against recipient's token account 36 | #[account()] 37 | pub recipient: AccountInfo<'info>, 38 | 39 | #[account( 40 | init_if_needed, 41 | payer = authority, 42 | associated_token::mint = mint, 43 | associated_token::authority = recipient 44 | )] 45 | pub recipient_token_account: Account<'info, TokenAccount>, 46 | 47 | #[account(address = sysvar::rent::ID)] 48 | pub rent: Sysvar<'info, Rent>, 49 | 50 | #[account(address = system_program::ID)] 51 | pub system_program: Program<'info, System>, 52 | 53 | #[account(address = anchor_spl::token::ID)] 54 | pub token_program: Program<'info, anchor_spl::token::Token>, 55 | } 56 | 57 | pub fn handler<'info>( 58 | ctx: Context<'_, '_, '_, 'info, Create<'info>>, 59 | mint_amount: u64, 60 | ) -> Result<()> { 61 | // get accounts 62 | let authority = &ctx.accounts.authority; 63 | let distributor = &mut ctx.accounts.distributor; 64 | let mint = &mut ctx.accounts.mint; 65 | let recipient = &ctx.accounts.recipient; 66 | let recipient_token_account = &ctx.accounts.recipient_token_account; 67 | let token_program = &ctx.accounts.token_program; 68 | 69 | // initialize distributor account 70 | distributor.new( 71 | authority.key(), 72 | recipient.key(), 73 | recipient_token_account.key(), 74 | mint.key(), 75 | mint_amount, 76 | )?; 77 | msg!("distributor: {:#?}", distributor); 78 | 79 | // delegate mint authority from payer (authority) to distributor account 80 | token::set_authority( 81 | CpiContext::new( 82 | token_program.to_account_info(), 83 | SetAuthority { 84 | account_or_mint: mint.to_account_info(), 85 | current_authority: authority.to_account_info(), 86 | }, 87 | ), 88 | AuthorityType::MintTokens, 89 | Some(distributor.key()), 90 | )?; 91 | 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/instructions/distribute.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{ 4 | prelude::*, 5 | solana_program::{system_program, sysvar}, 6 | }, 7 | anchor_spl::{ 8 | associated_token::AssociatedToken, 9 | token::{self, Mint, MintTo, TokenAccount}, 10 | }, 11 | clockwork_sdk::{ 12 | state::{Thread, ThreadAccount, ThreadResponse}, 13 | }, 14 | }; 15 | 16 | #[derive(Accounts)] 17 | pub struct Distribute<'info> { 18 | #[account(address = anchor_spl::associated_token::ID)] 19 | pub associated_token_program: Program<'info, AssociatedToken>, 20 | 21 | #[account( 22 | seeds = [SEED_DISTRIBUTOR, distributor.mint.as_ref(), distributor.authority.as_ref()], 23 | bump, 24 | has_one = mint, 25 | has_one = recipient, 26 | )] 27 | pub distributor: Account<'info, Distributor>, 28 | 29 | #[account( 30 | mut, 31 | signer, 32 | address = distributor_thread.pubkey(), 33 | constraint = distributor_thread.authority.eq(&distributor.authority), 34 | )] 35 | pub distributor_thread: Box>, 36 | 37 | #[account(mut)] 38 | pub mint: Account<'info, Mint>, 39 | 40 | #[account(mut)] 41 | pub payer: Signer<'info>, 42 | 43 | /// CHECK: manually validated against distributor account and recipient's token account 44 | pub recipient: AccountInfo<'info>, 45 | 46 | #[account( 47 | init_if_needed, 48 | payer = payer, 49 | associated_token::mint = mint, 50 | associated_token::authority = recipient 51 | )] 52 | pub recipient_token_account: Account<'info, TokenAccount>, 53 | 54 | #[account(address = sysvar::rent::ID)] 55 | pub rent: Sysvar<'info, Rent>, 56 | 57 | #[account(address = system_program::ID)] 58 | pub system_program: Program<'info, System>, 59 | 60 | #[account(address = anchor_spl::token::ID)] 61 | pub token_program: Program<'info, token::Token>, 62 | } 63 | 64 | pub fn handler<'info>(ctx: Context<'_, '_, '_, 'info, Distribute<'info>>) -> Result { 65 | // get accounts 66 | let distributor = &ctx.accounts.distributor; 67 | let mint = &ctx.accounts.mint; 68 | let recipient_token_account = &mut ctx.accounts.recipient_token_account; 69 | let token_program = &ctx.accounts.token_program; 70 | 71 | // get distributor bump 72 | let bump = *ctx.bumps.get("distributor").unwrap(); 73 | 74 | // mint to recipient' token account 75 | token::mint_to( 76 | CpiContext::new_with_signer( 77 | token_program.to_account_info(), 78 | MintTo { 79 | authority: distributor.to_account_info(), 80 | mint: mint.to_account_info(), 81 | to: recipient_token_account.to_account_info(), 82 | }, 83 | &[&[ 84 | SEED_DISTRIBUTOR, 85 | distributor.mint.as_ref(), 86 | distributor.authority.as_ref(), 87 | &[bump], 88 | ]], 89 | ), 90 | distributor.mint_amount, 91 | )?; 92 | 93 | Ok(ThreadResponse::default()) 94 | } 95 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create; 2 | pub mod distribute; 3 | pub mod update; 4 | 5 | pub use create::*; 6 | pub use distribute::*; 7 | pub use update::*; 8 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod distributor { 13 | use super::*; 14 | 15 | /* 16 | * initialize distributor account 17 | */ 18 | pub fn create<'info>( 19 | ctx: Context<'_, '_, '_, 'info, Create<'info>>, 20 | mint_amount: u64, 21 | ) -> Result<()> { 22 | create::handler(ctx, mint_amount) 23 | } 24 | 25 | /* 26 | * mint to recipient's ATA 27 | */ 28 | pub fn distribute<'info>( 29 | ctx: Context<'_, '_, '_, 'info, Distribute<'info>>, 30 | ) -> Result { 31 | distribute::handler(ctx) 32 | } 33 | 34 | /* 35 | * update recipient, mint amount, and thread schedule 36 | */ 37 | pub fn update<'info>( 38 | ctx: Context<'_, '_, '_, 'info, Update<'info>>, 39 | new_recipient: Option, 40 | mint_amount: Option, 41 | schedule: Option, 42 | ) -> Result<()> { 43 | update::handler(ctx, new_recipient, mint_amount, schedule) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/state/distributor.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, AnchorDeserialize}; 2 | 3 | pub const SEED_DISTRIBUTOR: &[u8] = b"distributor"; 4 | 5 | /** 6 | * Distributor 7 | */ 8 | #[account] 9 | #[derive(Debug)] 10 | pub struct Distributor { 11 | pub authority: Pubkey, 12 | pub mint: Pubkey, 13 | pub recipient: Pubkey, 14 | pub recipient_token_account: Pubkey, 15 | pub mint_amount: u64, 16 | } 17 | 18 | impl Distributor { 19 | pub fn pubkey(mint: Pubkey, authority: Pubkey) -> Pubkey { 20 | Pubkey::find_program_address( 21 | &[SEED_DISTRIBUTOR, mint.as_ref(), authority.as_ref()], 22 | &crate::ID, 23 | ).0 24 | } 25 | } 26 | 27 | impl TryFrom> for Distributor { 28 | type Error = Error; 29 | fn try_from(data: Vec) -> std::result::Result { 30 | Distributor::try_deserialize(&mut data.as_slice()) 31 | } 32 | } 33 | 34 | /** 35 | * DistributorAccount 36 | */ 37 | 38 | pub trait DistributorAccount { 39 | fn new( 40 | &mut self, 41 | authority: Pubkey, 42 | recipient: Pubkey, 43 | recipient_token_account: Pubkey, 44 | mint: Pubkey, 45 | mint_amount: u64, 46 | ) -> Result<()>; 47 | } 48 | 49 | impl DistributorAccount for Account<'_, Distributor> { 50 | fn new( 51 | &mut self, 52 | authority: Pubkey, 53 | recipient: Pubkey, 54 | recipient_token_account: Pubkey, 55 | mint: Pubkey, 56 | mint_amount: u64, 57 | ) -> Result<()> { 58 | self.authority = authority; 59 | self.recipient = recipient; 60 | self.recipient_token_account = recipient_token_account; 61 | self.mint = mint; 62 | self.mint_amount = mint_amount; 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /distributor/programs/distributor/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod distributor; 2 | 3 | pub use distributor::*; 4 | -------------------------------------------------------------------------------- /distributor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /event_stream/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /event_stream/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /event_stream/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | event_stream = "Mz49SjsVjXwxdwouKeEjvC3DUsDa8oZtVu7TtR4yGj5" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "localnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | -------------------------------------------------------------------------------- /event_stream/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | -------------------------------------------------------------------------------- /event_stream/README.md: -------------------------------------------------------------------------------- 1 | # **Event Stream Program** 2 | 3 | ## Workflow 4 | **0. Install and run the Clockwork CLI** 5 | ```bash 6 | cargo install -f --locked clockwork-cli 7 | clockwork localnet 8 | ``` 9 | 10 | **1. Program Side - Deploying the Handler Program** 11 | 12 | We start by defining an instruction to execute, that is the __"WHAT"__: 13 | - We have already prepared a handler. 14 | - You just have to deploy it using `./deploy.sh localnet` _(nothing fancy, the usual program ids and network switch)_. 15 | 16 | **2. Client Side - Creating a Thread** 17 | 18 | Time to switch perspective, we need to do some work off-chain now, we will create a Thread, that's the __"HOW"__: 19 | - Check the `tests` folder, we are using anchor tests as a client. 20 | - Run `anchor test --skip-local-validator`, this will create a __Thread__ that listens for a certain account and print logs whenever the 21 | account is updated. 22 | 23 | ## How do I know if it works? 24 | Let's see how we can observe our newly created Thread: 25 | - The prepared client, will also print the Solana Explorer url 26 | ```bash 27 | initialize tx: ✅ https://explorer.solana.com/tx/... 28 | ping tx: ✅ https://explorer.solana.com/tx/... 29 | ... 30 | ``` 31 | - If you have the Clockwork Cli installed, you can use the `clockwork` command 32 | ```bash 33 | clockwork thread get --address 34 | clockwork thread get 35 | ``` 36 | 37 | --- 38 | 39 | ## Common Errors 40 | Please refer to the [FAQ](https://github.com/clockwork-xyz/docs/blob/main/FAQ.md#common-errors). 41 | 42 | ## Questions 43 | Come build with us and ask us questions [Discord](https://discord.gg/epHsTsnUre)! 44 | -------------------------------------------------------------------------------- /event_stream/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /event_stream/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /event_stream/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@clockwork-xyz/sdk": "0.3.4", 8 | "@project-serum/anchor": "^0.26.0" 9 | }, 10 | "devDependencies": { 11 | "@types/bn.js": "^5.1.0", 12 | "@types/chai": "^4.3.0", 13 | "@types/mocha": "^9.0.0", 14 | "chai": "^4.3.4", 15 | "mocha": "^9.0.3", 16 | "prettier": "^2.6.2", 17 | "ts-mocha": "^10.0.0", 18 | "typescript": "^4.3.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "event_stream" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "event_stream" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.27.0", features = ["init-if-needed"]} 20 | clockwork-sdk = { version = "2.0.18" } 21 | 22 | # needed to fix the could not compile spl-token-2022 issue 23 | solana-program = "=1.14.16" 24 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("Mz49SjsVjXwxdwouKeEjvC3DUsDa8oZtVu7TtR4yGj5"); 4 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod initialize; 2 | pub mod ping; 3 | pub mod process_event; 4 | 5 | pub use initialize::*; 6 | pub use ping::*; 7 | pub use process_event::*; 8 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/instructions/ping.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct Ping<'info> { 5 | #[account(mut, seeds = [SEED_EVENT], bump)] 6 | pub event: Account<'info, Event>, 7 | 8 | #[account(mut)] 9 | pub signer: Signer<'info>, 10 | } 11 | 12 | pub fn handler(ctx: Context) -> Result<()> { 13 | // Get accounts 14 | let event = &mut ctx.accounts.event; 15 | let signer = &ctx.accounts.signer; 16 | 17 | // Initialize event account 18 | event.user = signer.key(); 19 | event.timestamp = Clock::get().unwrap().unix_timestamp; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/instructions/process_event.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::prelude::*, 4 | clockwork_sdk::state::Thread, 5 | }; 6 | 7 | #[derive(Accounts)] 8 | pub struct ProcessEvent<'info> { 9 | #[account(address = Authority::pubkey())] 10 | pub authority: Account<'info, Authority>, 11 | 12 | #[account(address = Event::pubkey())] 13 | pub event: Account<'info, Event>, 14 | 15 | #[account( 16 | address = event_thread.key(), 17 | signer, 18 | has_one = authority 19 | )] 20 | pub event_thread: Account<'info, Thread>, 21 | } 22 | 23 | pub fn handler(ctx: Context) -> Result<()> { 24 | // Get accounts 25 | let event = &mut ctx.accounts.event; 26 | 27 | // Initialize event account 28 | msg!( 29 | "Event was triggered by {} at {}", 30 | event.user, 31 | event.timestamp, 32 | ); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | use anchor_lang::prelude::*; 7 | use instructions::*; 8 | 9 | pub use id::ID; 10 | 11 | #[program] 12 | pub mod event_stream { 13 | use super::*; 14 | 15 | pub fn initialize(ctx: Context, thread_id: Vec) -> Result<()> { 16 | initialize::handler(ctx, thread_id) 17 | } 18 | 19 | pub fn reset(ctx: Context) -> Result<()> { 20 | initialize::reset(ctx) 21 | } 22 | 23 | pub fn ping(ctx: Context) -> Result<()> { 24 | ping::handler(ctx) 25 | } 26 | 27 | pub fn process_event(ctx: Context) -> Result<()> { 28 | process_event::handler(ctx) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/state/authority.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_AUTHORITY: &[u8] = b"authority"; 7 | 8 | /** 9 | * Authority 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Authority {} 15 | 16 | impl Authority { 17 | pub fn pubkey() -> Pubkey { 18 | Pubkey::find_program_address(&[SEED_AUTHORITY], &crate::ID).0 19 | } 20 | } 21 | 22 | impl TryFrom> for Authority { 23 | type Error = Error; 24 | fn try_from(data: Vec) -> std::result::Result { 25 | Authority::try_deserialize(&mut data.as_slice()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/state/event.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_EVENT: &[u8] = b"event"; 7 | 8 | /** 9 | * Fee 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Event { 15 | pub timestamp: i64, 16 | pub user: Pubkey, 17 | } 18 | 19 | impl Event { 20 | pub fn pubkey() -> Pubkey { 21 | Pubkey::find_program_address(&[SEED_EVENT], &crate::ID).0 22 | } 23 | } 24 | 25 | impl TryFrom> for Event { 26 | type Error = Error; 27 | fn try_from(data: Vec) -> std::result::Result { 28 | Event::try_deserialize(&mut data.as_slice()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /event_stream/programs/event_stream/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod authority; 2 | mod event; 3 | 4 | pub use authority::*; 5 | pub use event::*; 6 | -------------------------------------------------------------------------------- /event_stream/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /event_stream/yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /usr/local/bin/node /Users/garfield/.yarn/bin/yarn.js 3 | 4 | PATH: 5 | /Users/garfield/Developer/solana/bin:/usr/local/opt/libpq/bin:/Users/garfield/.yarn/bin:/Users/garfield/.config/yarn/global/node_modules/.bin:/opt/homebrew/opt/openssl@1.1/bin:/opt/homebrew/opt/openssl@1.1/bin:/Users/garfield/.local/share/solana/install/active_release/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Library/Apple/usr/bin:/usr/local/opt/libpq/bin:/Users/garfield/.yarn/bin:/Users/garfield/.config/yarn/global/node_modules/.bin:/opt/homebrew/opt/openssl@1.1/bin:/Users/garfield/.local/share/solana/install/active_release/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Users/garfield/.cargo/bin:/Users/garfield/Developer/solana/bin:/Users/garfield/Developer/solana/bin 6 | 7 | Yarn version: 8 | 1.22.15 9 | 10 | Node version: 11 | 14.18.0 12 | 13 | Platform: 14 | darwin x64 15 | 16 | Trace: 17 | Error: https://registry.yarnpkg.com/mocha: ESOCKETTIMEDOUT 18 | at ClientRequest. (/Users/garfield/.yarn/lib/cli.js:141512:19) 19 | at Object.onceWrapper (events.js:519:28) 20 | at ClientRequest.emit (events.js:400:28) 21 | at TLSSocket.emitRequestTimeout (_http_client.js:790:9) 22 | at Object.onceWrapper (events.js:519:28) 23 | at TLSSocket.emit (events.js:412:35) 24 | at TLSSocket.Socket._onTimeout (net.js:495:8) 25 | at listOnTimeout (internal/timers.js:557:17) 26 | at processTimers (internal/timers.js:500:7) 27 | 28 | npm manifest: 29 | { 30 | "scripts": { 31 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 32 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 33 | }, 34 | "dependencies": { 35 | "@project-serum/anchor": "^0.25.0" 36 | }, 37 | "devDependencies": { 38 | "chai": "^4.3.4", 39 | "mocha": "^9.0.3", 40 | "ts-mocha": "^10.0.0", 41 | "@types/bn.js": "^5.1.0", 42 | "@types/chai": "^4.3.0", 43 | "@types/mocha": "^9.0.0", 44 | "typescript": "^4.3.5", 45 | "prettier": "^2.6.2" 46 | } 47 | } 48 | 49 | yarn manifest: 50 | No manifest 51 | 52 | Lockfile: 53 | No lockfile 54 | -------------------------------------------------------------------------------- /hello_clockwork/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /hello_clockwork/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /hello_clockwork/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | 4 | [programs.devnet] 5 | hello_clockwork = "BRKkBxcCM85r1frqSKLFmmRXetqiCyqnrXG4iWt4VBP1" 6 | 7 | [programs.localnet] 8 | hello_clockwork = "BRKkBxcCM85r1frqSKLFmmRXetqiCyqnrXG4iWt4VBP1" 9 | 10 | [registry] 11 | url = "https://anchor.projectserum.com" 12 | 13 | [provider] 14 | cluster = "devnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /hello_clockwork/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | -------------------------------------------------------------------------------- /hello_clockwork/README.md: -------------------------------------------------------------------------------- 1 | # **0 - Hello, Clockwork** 2 | 3 | For a complete guide to this example project, please see to the [Clockwork docs](https://docs.clockwork.xyz/developers/guides/0-hello-clockwork). 4 | 5 | --- 6 | 7 | Testing locally: 8 | ```bash 9 | cargo install -f --locked clockwork-cli 10 | clockwork localnet 11 | ``` 12 | 13 | Get a new program id: 14 | ```bash 15 | ./new-program-id.sh 16 | ``` 17 | 18 | Run the tests and observe the logs: 19 | ```bash 20 | anchor test --skip-local-validator 21 | ``` 22 | -------------------------------------------------------------------------------- /hello_clockwork/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /hello_clockwork/new-program-id.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /hello_clockwork/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@clockwork-xyz/sdk": "0.3.0", 8 | "@project-serum/anchor": "^0.26.0" 9 | }, 10 | "devDependencies": { 11 | "@utils": "file:../utils", 12 | "@types/bn.js": "^5.1.0", 13 | "@types/chai": "^4.3.0", 14 | "@types/mocha": "^9.0.0", 15 | "chai": "^4.3.4", 16 | "mocha": "^9.0.3", 17 | "prettier": "^2.6.2", 18 | "ts-mocha": "^10.0.0", 19 | "typescript": "^4.3.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hello_clockwork/programs/hello_clockwork/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-clockwork" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "hello_clockwork" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [profile.release] 19 | overflow-checks = true 20 | 21 | [dependencies] 22 | anchor-lang = "0.26.0" 23 | clockwork-sdk = { version = "~2.0.1" } 24 | -------------------------------------------------------------------------------- /hello_clockwork/programs/hello_clockwork/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /hello_clockwork/programs/hello_clockwork/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("BRKkBxcCM85r1frqSKLFmmRXetqiCyqnrXG4iWt4VBP1"); 4 | 5 | #[program] 6 | pub mod hello_clockwork { 7 | use super::*; 8 | 9 | pub fn hello(_ctx: Context, name: String) -> Result<()> { 10 | msg!( 11 | "Hello, {}! The current time is: {}", 12 | name, 13 | Clock::get().unwrap().unix_timestamp 14 | ); 15 | 16 | Ok(()) 17 | } 18 | } 19 | 20 | #[derive(Accounts)] 21 | #[instruction(name: String)] 22 | pub struct Hello {} 23 | // Replace the above by this to enforce that the ix can only be run from a given thread 24 | // #[derive(Accounts)] 25 | // pub struct Hello<'info> { 26 | // #[account(address = thread.pubkey(), signer)] 27 | // pub thread: Account<'info, Thread>, 28 | // } 29 | -------------------------------------------------------------------------------- /hello_clockwork/tests/hello_clockwork.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as anchor from "@project-serum/anchor"; 3 | import { Program } from "@project-serum/anchor"; 4 | import { HelloClockwork } from "../target/types/hello_clockwork"; 5 | import { print_address, print_thread, print_tx, stream_program_logs } from "@utils"; 6 | 7 | // 0️⃣ Import the Clockwork SDK. 8 | import { ClockworkProvider } from "@clockwork-xyz/sdk"; 9 | 10 | describe("hello_clockwork", () => { 11 | const provider = anchor.AnchorProvider.env(); 12 | anchor.setProvider(provider); 13 | const wallet = provider.wallet; 14 | const program = anchor.workspace.HelloClockwork as Program; 15 | const clockworkProvider = ClockworkProvider.fromAnchorProvider(provider); 16 | 17 | print_address("🔗 HelloClockwork program", program.programId.toString()); 18 | 19 | it("It says hello", async () => { 20 | const tx = await program.methods.hello("world").rpc(); 21 | print_tx("🖊️ Hello", tx); 22 | }); 23 | 24 | it("It runs every 10 seconds", async () => { 25 | // 1️⃣ Prepare an instruction to be automated. 26 | const targetIx = await program.methods.hello("world").accounts({}).instruction(); 27 | 28 | // 2️⃣ Define a trigger condition for the thread. 29 | const trigger = { 30 | cron: { 31 | schedule: "*/10 * * * * * *", 32 | skippable: true, 33 | }, 34 | } 35 | 36 | // 3️⃣ Create the thread. 37 | try { 38 | const threadId = "hello_" + new Date().getTime() / 1000; 39 | const ix = await clockworkProvider.threadCreate( 40 | wallet.publicKey, // authority 41 | threadId, // id 42 | [targetIx], // instructions to execute 43 | trigger, // trigger condition 44 | anchor.web3.LAMPORTS_PER_SOL, // pre-fund amount 45 | ); 46 | const tx = new anchor.web3.Transaction().add(ix); 47 | const signature = await clockworkProvider.anchorProvider.sendAndConfirm(tx); 48 | 49 | const [threadAddress, threadBump] = clockworkProvider.getThreadPDA(wallet.publicKey, threadId) 50 | await print_thread(clockworkProvider, threadAddress); 51 | stream_program_logs(program.programId); 52 | } catch (e) { 53 | // ❌ 54 | // 'Program log: Instruction: ThreadCreate', 55 | // 'Program 11111111111111111111111111111111 invoke [2]', 56 | // 'Allocate: account Address { address: ..., base: None } already in use' 57 | // 58 | // -> If you encounter this error, the thread address you are trying to use is already in use. 59 | // You can change the threadId, to generate a new account address. 60 | // -> OR update the thread with a ThreadUpdate instruction (more on this in future guide) 61 | console.error(e); 62 | expect.fail(e); 63 | } 64 | }); 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /hello_clockwork/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /openbook_crank/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /openbook_crank/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /openbook_crank/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | [programs.localnet] 5 | openbook_crank = "5VgDg9um65qixinLJRci5exrn5MVjZZ3PpMbsDuSyP8P" 6 | 7 | [registry] 8 | url = "https://api.apr.dev" 9 | 10 | [provider] 11 | cluster = "mainnet" 12 | wallet = "~/.config/solana/id.json" 13 | 14 | [scripts] 15 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 16 | -------------------------------------------------------------------------------- /openbook_crank/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "client", 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /openbook_crank/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Openbook Crank Program

5 |
6 | 7 | --- 8 | 9 | ## Devnet 10 | TBD 11 | 12 | ## Localnet 13 | 14 | The workflow is pretty similar with __devnet__ with the exception that you are running your own validator. So you need: 15 | 1. The `solana-test-validator` which should be installed by default with Solana. 16 | 2. Install and run the Clockwork Engine _(geyser plugin)_ with the `solana-test-validator`. 17 | 18 | 19 | **0. Program Side - Preparing the Handler Program** 20 | 21 | We start by defining an instruction to execute, that is the __"WHAT"__: 22 | - We have already prepared a handler in `./programs/`. 23 | 24 | **1. Validator Setup - Run the validator with Clockwork** 25 | - Build and install the [Clockwork Engine](https://github.com/clockwork-xyz/clockwork#local-development) 26 | - Run `./clockwork_localnet.sh` to start a local validator with the Clockwork Engine + Openbook Programs + Our 27 | Handler Program from 👆. 28 | 29 | **2. Client Side - Creating a Thread** 30 | 31 | Time to switch perspective, we need to do some work off-chain now, we will create a Thread, that's the __"HOW"__: 32 | - Navigate to the `client` directory. _We have prepared a Rust client, but you 33 | can very well use any Solana client you prefer (JS, Python, etc)._ 34 | - Run `cargo run --features localnet`, this will create a __Thread__ that continuously calls your program handler, and so continously 35 | prints "Hello World". 36 | 37 | ## Common Errors 38 | Please refer to the [FAQ](https://github.com/clockwork-xyz/docs/blob/main/FAQ.md#common-errors). 39 | 40 | ## Questions 41 | Come build with us and ask us questions [Discord](https://discord.gg/epHsTsnUre)! 42 | -------------------------------------------------------------------------------- /openbook_crank/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openbook-crank-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | openbook-crank = { path = "../programs/openbook_crank", features = ["no-entrypoint"], version = "0.1.0" } 8 | clockwork-sdk = { version = "1.4.2" } 9 | clockwork-client = { version = "1.4.0" } 10 | anchor-spl = { version = "0.26.0", features = ["dex"] } 11 | anchor-lang = "0.26.0" 12 | solana-sdk = "1.13.5" 13 | solana-cli-config = "1.10.4" 14 | solana-client = "1.13.5" 15 | 16 | [features] 17 | localnet = ["openbook-crank/localnet"] 18 | -------------------------------------------------------------------------------- /openbook_crank/client/src/utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::prelude::*, 3 | clockwork_client::{Client, ClientResult}, 4 | solana_sdk::{ 5 | instruction::Instruction, 6 | signature::{read_keypair_file, Keypair}, 7 | transaction::Transaction, 8 | }, 9 | }; 10 | 11 | pub fn openbook_dex_pk() -> Pubkey { 12 | anchor_spl::dex::ID 13 | } 14 | 15 | pub fn sign_send_and_confirm_tx( 16 | client: &Client, 17 | ix: Vec, 18 | signers: Option>, 19 | label: String, 20 | ) -> ClientResult<()> { 21 | let mut tx; 22 | 23 | match signers { 24 | Some(signer_keypairs) => { 25 | tx = Transaction::new_signed_with_payer( 26 | &ix, 27 | Some(&client.payer_pubkey()), 28 | &signer_keypairs, 29 | client.get_latest_blockhash().unwrap(), 30 | ); 31 | } 32 | None => { 33 | tx = Transaction::new_with_payer(&ix, Some(&client.payer_pubkey())); 34 | } 35 | } 36 | 37 | tx.sign(&[client.payer()], client.latest_blockhash().unwrap()); 38 | 39 | // Send and confirm initialize tx 40 | match client.send_and_confirm_transaction(&tx) { 41 | Ok(sig) => println!("{} tx: ✅ https://explorer.solana.com/tx/{}", label, sig), 42 | Err(err) => println!("{} tx: ❌ {:#?}", label, err), 43 | } 44 | Ok(()) 45 | } 46 | 47 | pub fn print_explorer_link(address: Pubkey, label: String) -> ClientResult<()> { 48 | println!( 49 | "{}: https://explorer.solana.com/address/{}", 50 | label.to_string(), 51 | address, 52 | ); 53 | 54 | Ok(()) 55 | } 56 | 57 | pub fn default_client() -> Client { 58 | #[cfg(not(feature = "localnet"))] 59 | let host = "https://api.mainnet-beta.solana.com"; 60 | #[cfg(feature = "localnet")] 61 | let host = "http://localhost:8899"; 62 | 63 | let config_file = solana_cli_config::CONFIG_FILE.as_ref().unwrap().as_str(); 64 | let config = solana_cli_config::Config::load(config_file).unwrap(); 65 | let payer = read_keypair_file(&config.keypair_path).unwrap(); 66 | Client::new(payer, host.into()) 67 | } 68 | 69 | #[derive(Debug)] 70 | pub struct MarketKeys { 71 | pub market: Pubkey, 72 | // pub req_q: Pubkey, 73 | pub event_q: Pubkey, 74 | pub bids: Pubkey, 75 | pub asks: Pubkey, 76 | pub coin_mint: Pubkey, 77 | pub coin_vault: Pubkey, 78 | pub coin_wallet: Pubkey, 79 | pub pc_mint: Pubkey, 80 | pub pc_vault: Pubkey, 81 | pub pc_wallet: Pubkey, 82 | pub vault_signer: Pubkey, 83 | } 84 | -------------------------------------------------------------------------------- /openbook_crank/clockwork_localnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf test-ledger 4 | anchor build -- --features localnet 5 | 6 | clockwork localnet \ 7 | --bpf-program ~/examples/serum_crank/dex/serum_dex-keypair.json \ 8 | --bpf-program ~/examples/serum_crank/dex/serum_dex.so \ 9 | --bpf-program ~/examples/serum_crank/target/deploy/serum_crank-keypair.json \ 10 | --bpf-program ~/examples/serum_crank/target/deploy/serum_crank.so 11 | 12 | -------------------------------------------------------------------------------- /openbook_crank/dex/serum_dex-keypair.json: -------------------------------------------------------------------------------- 1 | [231, 214, 181, 15, 221, 233, 213, 180, 115, 21, 20, 226, 27, 71, 62, 78, 10, 133, 157, 242, 5, 142, 116, 149, 195, 19, 210, 163, 158, 231, 11, 152, 80, 77, 255, 84, 158, 246, 206, 140, 91, 170, 85, 153, 188, 43, 195, 25, 110, 252, 96, 237, 212, 70, 202, 100, 158, 81, 190, 1, 209, 138, 121, 235] -------------------------------------------------------------------------------- /openbook_crank/dex/serum_dex.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clockwork-xyz/examples/eaace6e6625d25d8005bad56573c90eb94719d99/openbook_crank/dex/serum_dex.so -------------------------------------------------------------------------------- /openbook_crank/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /openbook_crank/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openbook-crank" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "openbook_crank" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | localnet = [] 18 | devnet = ["anchor-spl/devnet"] 19 | 20 | [profile.release] 21 | overflow-checks = true 22 | 23 | [dependencies] 24 | anchor-spl = { version = "0.26.0", features = ["dex"] } 25 | anchor-lang = "0.26.0" 26 | clockwork-sdk = { version = "1.4.2" } 27 | safe-transmute = "0.11.0" 28 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("5VgDg9um65qixinLJRci5exrn5MVjZZ3PpMbsDuSyP8P"); 4 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/instructions/delete.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct Delete<'info> { 5 | #[account()] 6 | pub authority: Signer<'info>, 7 | 8 | #[account(mut)] 9 | pub close_to: SystemAccount<'info>, 10 | 11 | #[account( 12 | mut, 13 | seeds = [ 14 | SEED_CRANK, 15 | crank.authority.as_ref(), 16 | crank.market.as_ref(), 17 | ], 18 | bump, 19 | has_one = authority, 20 | close = close_to 21 | )] 22 | pub crank: Account<'info, Crank>, 23 | } 24 | 25 | pub fn handler(_ctx: Context) -> Result<()> { 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/instructions/initialize.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::system_program}, 4 | anchor_spl::dex::serum_dex::state::Market, 5 | std::mem::size_of, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | pub struct Initialize<'info> { 10 | #[account( 11 | init, 12 | payer = signer, 13 | seeds = [ 14 | SEED_CRANK, 15 | signer.key().as_ref(), 16 | market.key().as_ref(), 17 | ], 18 | bump, 19 | space = 8 + size_of::(), 20 | )] 21 | pub crank: Box>, 22 | 23 | pub dex_program: Program<'info, OpenBookDex>, 24 | 25 | /// CHECK: this account is manually verified in handler 26 | #[account()] 27 | pub event_queue: AccountInfo<'info>, 28 | 29 | /// CHECK: this account is manually verified in handler 30 | #[account()] 31 | pub market: AccountInfo<'info>, 32 | 33 | #[account(mut)] 34 | pub signer: Signer<'info>, 35 | 36 | #[account(address = system_program::ID)] 37 | pub system_program: Program<'info, System>, 38 | } 39 | 40 | pub fn handler<'info>( 41 | ctx: Context<'_, '_, '_, 'info, Initialize<'info>>, 42 | ) -> Result<()> { 43 | // Get accounts 44 | let crank = &mut ctx.accounts.crank; 45 | let dex_program = &ctx.accounts.dex_program; 46 | let event_queue = &ctx.accounts.event_queue; 47 | let market = &ctx.accounts.market; 48 | let signer = &ctx.accounts.signer; 49 | 50 | // validate market 51 | let market_data = Market::load(market, &dex_program.key()).unwrap(); 52 | let val = unsafe { std::ptr::addr_of!(market_data.event_q).read_unaligned() }; 53 | let market_event_queue = Pubkey::new(safe_transmute::to_bytes::transmute_one_to_bytes( 54 | core::convert::identity(&val), 55 | )); 56 | 57 | require_keys_eq!(event_queue.key(), market_event_queue); 58 | 59 | // initialize crank account 60 | crank.new( 61 | signer.key(), 62 | market.key(), 63 | 10, 64 | )?; 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod consume_events; 2 | pub mod delete; 3 | pub mod initialize; 4 | 5 | pub use consume_events::*; 6 | pub use delete::*; 7 | pub use initialize::*; 8 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod openbook_crank { 13 | use super::*; 14 | 15 | /* 16 | * initialize crank account 17 | */ 18 | pub fn initialize<'info>(ctx: Context<'_, '_, '_, 'info, Initialize<'info>>) -> Result<()> { 19 | initialize::handler(ctx) 20 | } 21 | 22 | /* 23 | * crank open orders 24 | */ 25 | pub fn consume_events<'info>( 26 | ctx: Context<'_, '_, '_, 'info, ConsumeEvents<'info>>, 27 | ) -> Result { 28 | consume_events::handler(ctx) 29 | } 30 | 31 | /* 32 | * delete crank account 33 | */ 34 | pub fn delete<'info>(ctx: Context<'_, '_, '_, 'info, Delete<'info>>) -> Result<()> { 35 | delete::handler(ctx) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/state/crank.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_CRANK: &[u8] = b"crank"; 7 | 8 | /** 9 | * Crank 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Crank { 15 | pub authority: Pubkey, 16 | pub market: Pubkey, 17 | pub limit: u16, 18 | } 19 | 20 | impl Crank { 21 | pub fn pubkey(authority: Pubkey, market: Pubkey) -> Pubkey { 22 | Pubkey::find_program_address( 23 | &[SEED_CRANK, authority.as_ref(), market.as_ref()], 24 | &crate::ID, 25 | ) 26 | .0 27 | } 28 | } 29 | 30 | impl TryFrom> for Crank { 31 | type Error = Error; 32 | fn try_from(data: Vec) -> std::result::Result { 33 | Crank::try_deserialize(&mut data.as_slice()) 34 | } 35 | } 36 | 37 | /** 38 | * CrankAccount 39 | */ 40 | 41 | pub trait CrankAccount { 42 | fn new(&mut self, authority: Pubkey, market: Pubkey, limit: u16) -> Result<()>; 43 | } 44 | 45 | impl CrankAccount for Account<'_, Crank> { 46 | fn new(&mut self, authority: Pubkey, market: Pubkey, limit: u16) -> Result<()> { 47 | self.authority = authority; 48 | self.market = market; 49 | self.limit = limit; 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod crank; 2 | mod openbook_dex; 3 | 4 | pub use crank::*; 5 | pub use openbook_dex::*; 6 | -------------------------------------------------------------------------------- /openbook_crank/programs/openbook_crank/src/state/openbook_dex.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct OpenBookDex; 5 | 6 | impl Id for OpenBookDex { 7 | fn id() -> Pubkey { 8 | anchor_spl::dex::ID 9 | 10 | // ORIGINAL OPENBOOK DEX CODE BELOW: 11 | // #[cfg(not(feature = "devnet"))] 12 | // anchor_lang::solana_program::declare_id!("srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"); 13 | // 14 | // #[cfg(feature = "devnet")] 15 | // anchor_lang::solana_program::declare_id!("EoTcMgcDRTJVZDMZWBoU6rhYHZfkNTVEAfz3uUJRcYGj"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /openbook_crank/tests/openbook_crank.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { OpenbookCrank } from "../target/types/openbook_crank"; 4 | 5 | describe("openbook_crank", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.OpenbookCrank as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /openbook_crank/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /openbook_dca/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /openbook_dca/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /openbook_dca/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.mainnet] 6 | openbook_dca = "6mrizDvtdQdg5J79GRCbBDZvmKjDjwuUcZNM5zj4TGdD" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "mainnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | -------------------------------------------------------------------------------- /openbook_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "client", 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /openbook_dca/README.md: -------------------------------------------------------------------------------- 1 | # **Openbook DCA Program** 2 | 3 | ## Prerequisites 4 | - Make sure you have both the [solana cli](https://docs.solana.com/cli/install-solana-cli-tools) and [anchor cli](https://project-serum.github.io/anchor/getting-started/installation.html#build-from-source-for-other-operating-systems) installed on your computer. 5 | - clone the [clockwork repo](https://github.com/clockwork-xyz/clockwork/) locally to your machine 6 | 7 | ## Localnet 8 | ### Openbook DCA 9 | - run `anchor build` in the root directory of `openbook_dca` 10 | - run `solana address -k target/deploy/openbook_dca-keypair.json` to get your program's ID 11 | - copy that ID and replace it with the Program ID in `id.rs` 12 | - run `anchor build` again 13 | ### Openbook Crank 14 | - navigate to the `openbook_crank` directory of the examples repo 15 | - run `anchor build` in the root directory of `openbook_crank` 16 | - run `solana address -k target/deploy/openbook_crank-keypair.json` to get your program's ID 17 | - copy that ID and replace it with the Program ID in `id.rs` 18 | - run `anchor build` again 19 | ### Deployment 20 | - be sure to set your solana config to devnet with `solana config set --url http://localhost:8899` 21 | - if you have the [clockwork repo](https://github.com/clockwork-xyz/clockwork/#getting-started) and you've followed the [getting started](https://github.com/clockwork-xyz/clockwork/#getting-started) guide on how to build from source you can run the following command 22 | ```bash 23 | clockwork localnet --bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin /clockwork-xyz/examples/openbook_dca/dex/serum_dex.so --bpf-program /clockwork-xyz/examples/openbook_crank/target/deploy/openbook_crank-keypair.json /clockwork-xyz/examples/openbook_dca/target/deploy/openbook_crank.so --bpf-program /clockwork-xyz/examples/openbook_dca/target/deploy/openbook_dca-keypair.json /clockwork-xyz/examples/openbook_dca/target/deploy/openbook_dca.so 24 | ``` 25 | ### Client 26 | - navigate to the `client` directory 27 | - run `cargo run` -------------------------------------------------------------------------------- /openbook_dca/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dca-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | openbook-dca = { path = "../programs/openbook_dca", features = ["no-entrypoint"], version = "0.1.0" } 8 | clockwork-client = "1.4.2" 9 | anchor-spl = { version = "0.26.0", features = ["dex"] } 10 | anchor-lang = "0.26.0" 11 | solana-sdk = "1.14.12" 12 | solana-cli-config = "1.14.12" -------------------------------------------------------------------------------- /openbook_dca/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /openbook_dca/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /openbook_dca/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openbook-dca" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "openbook_dca" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-spl = { version = "0.26.0", features = ["dex"] } 20 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 21 | clockwork-sdk = "1.4.2" 22 | safe-transmute = "0.11.0" -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("6mrizDvtdQdg5J79GRCbBDZvmKjDjwuUcZNM5zj4TGdD"); 4 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/instructions/dca_delete.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct DcaDelete<'info> { 5 | /// The authority (owner) of the dca. 6 | #[account()] 7 | pub authority: Signer<'info>, 8 | 9 | /// The address to return the data rent lamports to. 10 | #[account(mut)] 11 | pub close_to: SystemAccount<'info>, 12 | 13 | #[account( 14 | mut, 15 | seeds = [ 16 | SEED_DCA, 17 | dca.authority.as_ref(), 18 | dca.market.as_ref(), 19 | ], 20 | bump, 21 | has_one = authority, 22 | close = close_to 23 | )] 24 | pub dca: Account<'info, Dca>, 25 | } 26 | 27 | pub fn handler(_ctx: Context) -> Result<()> { 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/instructions/dca_update.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::system_program}, 4 | }; 5 | 6 | #[derive(Accounts)] 7 | #[instruction(swap_amount: u64)] 8 | pub struct DcaUpdate<'info> { 9 | #[account( 10 | mut, 11 | seeds = [SEED_DCA, payer.key().as_ref(), market.key().as_ref()], 12 | bump, 13 | has_one = market 14 | )] 15 | pub dca: Account<'info, Dca>, 16 | 17 | /// CHECK: 18 | pub market: AccountInfo<'info>, 19 | 20 | #[account(mut)] 21 | pub payer: Signer<'info>, 22 | 23 | #[account(address = system_program::ID)] 24 | pub system_program: Program<'info, System>, 25 | } 26 | 27 | pub fn handler<'info>(ctx: Context>, swap_amount: u64) -> Result<()> { 28 | let dca = &mut ctx.accounts.dca; 29 | 30 | dca.swap_amount = swap_amount; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dca_create; 2 | pub mod dca_delete; 3 | pub mod dca_update; 4 | pub mod swap; 5 | 6 | pub use dca_create::*; 7 | pub use dca_delete::*; 8 | pub use dca_update::*; 9 | pub use swap::*; 10 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod openbook_dca { 13 | use super::*; 14 | 15 | /* 16 | * initialize dca and open orders accounts 17 | */ 18 | pub fn dca_create<'info>( 19 | ctx: Context<'_, '_, '_, 'info, DcaCreate<'info>>, 20 | swap_amount: u64, 21 | ) -> Result<()> { 22 | dca_create::handler(ctx, swap_amount) 23 | } 24 | 25 | /* 26 | * update dca account's swap amount 27 | */ 28 | pub fn dca_update<'info>(ctx: Context>, swap_amount: u64) -> Result<()> { 29 | dca_update::handler(ctx, swap_amount) 30 | } 31 | 32 | /* 33 | * delete dca account 34 | */ 35 | pub fn dca_delete<'info>(ctx: Context>) -> Result<()> { 36 | dca_delete::handler(ctx) 37 | } 38 | 39 | /* 40 | * transfer pc swap_amount from authority to dca 41 | * place order on openbook dex 42 | * settle funds 43 | * transfer coin token balance from dca to authority 44 | */ 45 | pub fn swap<'info>(ctx: Context<'_, '_, '_, 'info, Swap<'info>>) -> Result<()> { 46 | swap::handler(ctx) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/state/dca.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_DCA: &[u8] = b"dca"; 7 | 8 | /** 9 | * Dca 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Dca { 15 | pub market: Pubkey, 16 | pub authority: Pubkey, 17 | pub pc_mint: Pubkey, 18 | pub coin_mint: Pubkey, 19 | pub swap_amount: u64, 20 | } 21 | 22 | impl Dca { 23 | pub fn pubkey(authority: Pubkey, market: Pubkey) -> Pubkey { 24 | Pubkey::find_program_address(&[SEED_DCA, authority.as_ref(), market.as_ref()], &crate::ID).0 25 | } 26 | } 27 | 28 | impl TryFrom> for Dca { 29 | type Error = Error; 30 | fn try_from(data: Vec) -> std::result::Result { 31 | Dca::try_deserialize(&mut data.as_slice()) 32 | } 33 | } 34 | 35 | /** 36 | * DCAAccount 37 | */ 38 | 39 | pub trait DcaAccount { 40 | fn new( 41 | &mut self, 42 | authority: Pubkey, 43 | market: Pubkey, 44 | pc_mint: Pubkey, 45 | coin_mint: Pubkey, 46 | swap_amount: u64, 47 | ) -> Result<()>; 48 | } 49 | 50 | impl DcaAccount for Account<'_, Dca> { 51 | fn new( 52 | &mut self, 53 | authority: Pubkey, 54 | market: Pubkey, 55 | pc_mint: Pubkey, 56 | coin_mint: Pubkey, 57 | swap_amount: u64, 58 | ) -> Result<()> { 59 | self.authority = authority; 60 | self.market = market; 61 | self.pc_mint = pc_mint; 62 | self.coin_mint = coin_mint; 63 | self.swap_amount = swap_amount; 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod dca; 2 | mod openbook_dex; 3 | 4 | pub use dca::*; 5 | pub use openbook_dex::*; 6 | -------------------------------------------------------------------------------- /openbook_dca/programs/openbook_dca/src/state/openbook_dex.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct OpenBookDex; 5 | 6 | impl Id for OpenBookDex { 7 | fn id() -> Pubkey { 8 | anchor_spl::dex::ID 9 | 10 | // ORIGINAL OPENBOOK DEX CODE BELOW: 11 | // #[cfg(not(feature = "devnet"))] 12 | // anchor_lang::solana_program::declare_id!("srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"); 13 | // 14 | // #[cfg(feature = "devnet")] 15 | // anchor_lang::solana_program::declare_id!("EoTcMgcDRTJVZDMZWBoU6rhYHZfkNTVEAfz3uUJRcYGj"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /openbook_dca/tests/openbook_dca.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { OpenbookDca } from "../target/types/openbook_dca"; 4 | 5 | describe("openbook_dca", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.OpenbookDca as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /openbook_dca/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /orca_dca/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /orca_dca/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /orca_dca/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.mainnet] 6 | orca_dca = "DjKusZzdgdobu1yrh2qKLdcabzWRDLGECcajgfm2VCSJ" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "mainnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | -------------------------------------------------------------------------------- /orca_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | "client" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /orca_dca/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orca-dca-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | orca-dca = { path = "../programs/orca_dca", features = ["no-entrypoint"], version = "0.1.0" } 10 | clockwork-client = "1.4.2" 11 | anchor-lang = "0.26.0" 12 | anchor-spl = "0.26.0" 13 | solana-sdk = "1.14.12" 14 | solana-cli-config = "1.14.12" -------------------------------------------------------------------------------- /orca_dca/client/src/utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::prelude::*, 3 | clockwork_client::{Client, ClientResult}, 4 | solana_sdk::{ 5 | instruction::Instruction, 6 | signature::{read_keypair_file, Keypair}, 7 | transaction::Transaction, 8 | }, 9 | }; 10 | 11 | pub fn sign_send_and_confirm_tx( 12 | client: &Client, 13 | ix: Vec, 14 | signers: Option>, 15 | label: String, 16 | ) -> ClientResult<()> { 17 | let mut tx; 18 | 19 | match signers { 20 | Some(signer_keypairs) => { 21 | tx = Transaction::new_signed_with_payer( 22 | &ix, 23 | Some(&client.payer_pubkey()), 24 | &signer_keypairs, 25 | client.get_latest_blockhash().unwrap(), 26 | ); 27 | } 28 | None => { 29 | tx = Transaction::new_with_payer(&ix, Some(&client.payer_pubkey())); 30 | } 31 | } 32 | 33 | tx.sign(&[client.payer()], client.latest_blockhash().unwrap()); 34 | 35 | // Send and confirm initialize tx 36 | match client.send_and_confirm_transaction(&tx) { 37 | Ok(sig) => println!("{} tx: ✅ https://explorer.solana.com/tx/{}", label, sig), 38 | Err(err) => println!("{} tx: ❌ {:#?}", label, err), 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn default_client() -> Client { 44 | #[cfg(not(feature = "localnet"))] 45 | let host = "https://api.mainnet-beta.solana.com"; 46 | #[cfg(feature = "localnet")] 47 | let host = "http://localhost:8899"; 48 | 49 | let config_file = solana_cli_config::CONFIG_FILE.as_ref().unwrap().as_str(); 50 | let config = solana_cli_config::Config::load(config_file).unwrap(); 51 | let payer = read_keypair_file(&config.keypair_path).unwrap(); 52 | Client::new(payer, host.into()) 53 | } 54 | 55 | pub fn print_explorer_link(address: Pubkey, label: String) -> ClientResult<()> { 56 | println!( 57 | "{}: https://explorer.solana.com/address/{}", 58 | label.to_string(), 59 | address, 60 | ); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[derive(Debug)] 66 | pub struct PoolParams { 67 | pub address: Pubkey, 68 | pub authority: Pubkey, 69 | pub pool_token_mint: Pubkey, 70 | pub fee_account: Pubkey, 71 | pub pool_a_vault: Pubkey, 72 | pub pool_b_vault: Pubkey, 73 | pub token_ids: (Pubkey, Pubkey), 74 | } 75 | -------------------------------------------------------------------------------- /orca_dca/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /orca_dca/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orca-dca" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "orca_dca" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 20 | anchor-spl = "0.26.0" 21 | clockwork-sdk = "1.4.2" 22 | spl-token-swap = { version ="3.0.0", features = ["no-entrypoint"] } -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("DjKusZzdgdobu1yrh2qKLdcabzWRDLGECcajgfm2VCSJ"); 4 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/instructions/dca_create.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{ 4 | prelude::*, 5 | solana_program::system_program, 6 | }, 7 | anchor_spl::{ 8 | associated_token::AssociatedToken, 9 | token::{self, Mint, TokenAccount}, 10 | }, 11 | std::mem::size_of, 12 | }; 13 | 14 | #[derive(Accounts)] 15 | #[instruction( 16 | amount_in: u64, 17 | minimum_amount_out: u64, 18 | )] 19 | pub struct DcaCreate<'info> { 20 | #[account(address = anchor_spl::associated_token::ID)] 21 | pub associated_token_program: Program<'info, AssociatedToken>, 22 | 23 | #[account(mut)] 24 | pub authority: Signer<'info>, 25 | 26 | #[account( 27 | mut, 28 | associated_token::authority = authority, 29 | associated_token::mint = a_mint, 30 | )] 31 | pub authority_a_vault: Box>, 32 | 33 | /// CHECK: 34 | pub a_mint: Box>, 35 | 36 | #[account( 37 | init_if_needed, 38 | payer = authority, 39 | associated_token::authority = authority, 40 | associated_token::mint = b_mint, 41 | )] 42 | pub authority_b_vault: Box>, 43 | 44 | /// CHECK: 45 | pub b_mint: Box>, 46 | 47 | #[account( 48 | init, 49 | seeds = [ 50 | SEED_DCA, 51 | authority.key().as_ref(), 52 | a_mint.key().as_ref(), 53 | b_mint.key().as_ref() 54 | ], 55 | bump, 56 | payer = authority, 57 | space = 8 + size_of::(), 58 | )] 59 | pub dca: Box>, 60 | 61 | #[account( 62 | init_if_needed, 63 | payer = authority, 64 | associated_token::mint = a_mint, 65 | associated_token::authority = dca 66 | )] 67 | pub dca_a_vault: Box>, 68 | 69 | #[account( 70 | init_if_needed, 71 | payer = authority, 72 | associated_token::mint = b_mint, 73 | associated_token::authority = dca 74 | )] 75 | pub dca_b_vault: Box>, 76 | 77 | #[account(address = system_program::ID)] 78 | pub system_program: Program<'info, System>, 79 | 80 | #[account(address = anchor_spl::token::ID)] 81 | pub token_program: Program<'info, anchor_spl::token::Token>, 82 | } 83 | 84 | pub fn handler<'info>( 85 | ctx: Context<'_, '_, '_, 'info, DcaCreate<'info>>, 86 | amount_in: u64, 87 | minimum_amount_out: u64, 88 | ) -> Result<()> { 89 | // Get accounts 90 | let authority = &ctx.accounts.authority; 91 | let authority_a_vault = &mut ctx.accounts.authority_a_vault; 92 | let a_mint = &ctx.accounts.a_mint; 93 | let b_mint = &ctx.accounts.b_mint; 94 | let dca = &mut ctx.accounts.dca; 95 | let token_program = &ctx.accounts.token_program; 96 | 97 | // initialize dca account 98 | dca.new( 99 | authority.key(), 100 | a_mint.key(), 101 | b_mint.key(), 102 | amount_in, 103 | minimum_amount_out, 104 | )?; 105 | 106 | // Approve the dca account to spend from the authority's token account. 107 | token::approve( 108 | CpiContext::new( 109 | token_program.to_account_info(), 110 | token::Approve { 111 | to: authority_a_vault.to_account_info(), 112 | delegate: dca.to_account_info(), 113 | authority: authority.to_account_info(), 114 | }, 115 | ), 116 | u64::MAX, 117 | )?; 118 | 119 | Ok(()) 120 | } 121 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/instructions/dca_delete.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct DcaDelete<'info> { 5 | /// The authority (owner) of the dca. 6 | #[account()] 7 | pub authority: Signer<'info>, 8 | 9 | /// The address to return the data rent lamports to. 10 | #[account(mut)] 11 | pub close_to: SystemAccount<'info>, 12 | 13 | #[account( 14 | mut, 15 | seeds = [ 16 | SEED_DCA, 17 | dca.authority.as_ref(), 18 | dca.a_mint.as_ref(), 19 | dca.b_mint.as_ref(), 20 | ], 21 | bump, 22 | has_one = authority, 23 | close = close_to 24 | )] 25 | pub dca: Account<'info, Dca>, 26 | } 27 | 28 | pub fn handler(_ctx: Context) -> Result<()> { 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/instructions/dca_update.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::system_program}, 4 | }; 5 | 6 | #[derive(Accounts)] 7 | #[instruction(amount_in: Option, minimum_amount_in: Option)] 8 | pub struct DcaUpdate<'info> { 9 | #[account( 10 | mut, 11 | seeds = [ 12 | SEED_DCA, 13 | dca.authority.as_ref(), 14 | dca.a_mint.as_ref(), 15 | dca.b_mint.as_ref() 16 | ], 17 | bump, 18 | )] 19 | pub dca: Account<'info, Dca>, 20 | 21 | #[account(mut)] 22 | pub payer: Signer<'info>, 23 | 24 | #[account(address = system_program::ID)] 25 | pub system_program: Program<'info, System>, 26 | } 27 | 28 | pub fn handler<'info>(ctx: Context>, amount_in: Option, minimum_amount_out: Option) -> Result<()> { 29 | let dca = &mut ctx.accounts.dca; 30 | 31 | if let Some(ai) = amount_in { 32 | dca.amount_in = ai; 33 | } 34 | 35 | if let Some(mao) = minimum_amount_out { 36 | dca.minimum_amount_out = mao; 37 | } 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dca_create; 2 | pub mod dca_delete; 3 | pub mod dca_update; 4 | pub mod proxy_swap; 5 | 6 | pub use dca_create::*; 7 | pub use dca_delete::*; 8 | pub use dca_update::*; 9 | pub use proxy_swap::*; 10 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod orca_dca { 13 | use super::*; 14 | 15 | /* 16 | * initialize dca account with swap params 17 | */ 18 | pub fn dca_create<'info>( 19 | ctx: Context<'_, '_, '_, 'info, DcaCreate<'info>>, 20 | amount_in: u64, 21 | minimum_amount_out: u64, 22 | ) -> Result<()> { 23 | dca_create::handler(ctx, amount_in, minimum_amount_out) 24 | } 25 | 26 | /* 27 | * update swap amount 28 | */ 29 | pub fn dca_update<'info>( 30 | ctx: Context<'_, '_, '_, 'info, DcaUpdate<'info>>, 31 | amount_in: Option, 32 | minimum_amount_out: Option, 33 | ) -> Result<()> { 34 | dca_update::handler(ctx, amount_in, minimum_amount_out) 35 | } 36 | 37 | /* 38 | * delete dca account 39 | */ 40 | pub fn dca_delete<'info>(ctx: Context<'_, '_, '_, 'info, DcaDelete<'info>>) -> Result<()> { 41 | dca_delete::handler(ctx) 42 | } 43 | 44 | /* 45 | * swap on orca whirlpool 46 | */ 47 | pub fn proxy_swap<'info>(ctx: Context<'_, '_, '_, 'info, ProxySwap<'info>>) -> Result<()> { 48 | proxy_swap::handler(ctx) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/state/dca.rs: -------------------------------------------------------------------------------- 1 | use {anchor_lang::prelude::*, std::convert::TryFrom}; 2 | 3 | pub const SEED_DCA: &[u8] = b"dca"; 4 | 5 | /** 6 | * Dca 7 | */ 8 | 9 | #[account] 10 | #[derive(Debug)] 11 | pub struct Dca { 12 | pub authority: Pubkey, 13 | pub a_mint: Pubkey, 14 | pub b_mint: Pubkey, 15 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 16 | pub amount_in: u64, 17 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 18 | pub minimum_amount_out: u64, 19 | } 20 | 21 | impl Dca { 22 | pub fn pubkey(authority: Pubkey, a_mint: Pubkey, b_mint: Pubkey) -> Pubkey { 23 | Pubkey::find_program_address( 24 | &[ 25 | SEED_DCA, 26 | authority.as_ref(), 27 | a_mint.as_ref(), 28 | b_mint.as_ref(), 29 | ], 30 | &crate::ID, 31 | ) 32 | .0 33 | } 34 | } 35 | 36 | impl TryFrom> for Dca { 37 | type Error = Error; 38 | fn try_from(data: Vec) -> std::result::Result { 39 | Dca::try_deserialize(&mut data.as_slice()) 40 | } 41 | } 42 | 43 | /** 44 | * DCAAccount 45 | */ 46 | 47 | pub trait DcaAccount { 48 | fn new( 49 | &mut self, 50 | authority: Pubkey, 51 | a_mint: Pubkey, 52 | b_mint: Pubkey, 53 | amount_in: u64, 54 | minimum_amount_out: u64, 55 | ) -> Result<()>; 56 | } 57 | 58 | impl DcaAccount for Account<'_, Dca> { 59 | fn new( 60 | &mut self, 61 | authority: Pubkey, 62 | a_mint: Pubkey, 63 | b_mint: Pubkey, 64 | amount_in: u64, 65 | minimum_amount_out: u64, 66 | ) -> Result<()> { 67 | self.authority = authority; 68 | self.a_mint = a_mint; 69 | self.b_mint = b_mint; 70 | self.amount_in = amount_in; 71 | self.minimum_amount_out = minimum_amount_out; 72 | Ok(()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /orca_dca/programs/orca_dca/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod dca; 2 | 3 | pub use dca::*; 4 | -------------------------------------------------------------------------------- /orca_dca/tests/orca_dca.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { OrcaDca } from "../target/types/orca_dca"; 4 | 5 | describe("orca_dca", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.OrcaDca as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /orca_dca/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.mainnet] 6 | orca_whirlpool_dca = "FMQ5m4PXuEBsN7fhxUXEfWJd8ywPMrg9AeZGgjHNLucP" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "mainnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | "crates/*", 5 | "client" 6 | ] 7 | 8 | [profile.release] 9 | overflow-checks = true 10 | lto = "fat" 11 | codegen-units = 1 12 | [profile.release.build-override] 13 | opt-level = 3 14 | incremental = false 15 | codegen-units = 1 16 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orca-whirlpool-dca-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [features] 8 | delete = [] 9 | 10 | [dependencies] 11 | orca-whirlpool-dca = { path = "../programs/orca_whirlpool_dca", features = ["no-entrypoint"], version = "0.1.0" } 12 | clockwork-client = "1.4.2" 13 | anchor-lang = "0.26.0" 14 | anchor-spl = "0.26.0" 15 | solana-sdk = "1.14.12" 16 | solana-cli-config = "1.14.12" 17 | whirlpool = { path = "../crates/whirlpool" } 18 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/client/src/utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::prelude::*, 3 | clockwork_client::{Client, ClientResult}, 4 | solana_sdk::{ 5 | instruction::Instruction, 6 | signature::{read_keypair_file, Keypair}, 7 | transaction::Transaction, 8 | }, 9 | }; 10 | 11 | pub fn sign_send_and_confirm_tx( 12 | client: &Client, 13 | ix: Vec, 14 | signers: Option>, 15 | label: String, 16 | ) -> ClientResult<()> { 17 | let mut tx; 18 | 19 | match signers { 20 | Some(signer_keypairs) => { 21 | tx = Transaction::new_signed_with_payer( 22 | &ix, 23 | Some(&client.payer_pubkey()), 24 | &signer_keypairs, 25 | client.get_latest_blockhash().unwrap(), 26 | ); 27 | } 28 | None => { 29 | tx = Transaction::new_with_payer(&ix, Some(&client.payer_pubkey())); 30 | } 31 | } 32 | 33 | tx.sign(&[client.payer()], client.latest_blockhash().unwrap()); 34 | 35 | // Send and confirm initialize tx 36 | match client.send_and_confirm_transaction(&tx) { 37 | Ok(sig) => println!("{} tx: ✅ https://explorer.solana.com/tx/{}", label, sig), 38 | Err(err) => println!("{} tx: ❌ {:#?}", label, err), 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn default_client() -> Client { 44 | #[cfg(not(feature = "localnet"))] 45 | let host = "https://api.mainnet-beta.solana.com"; 46 | #[cfg(feature = "localnet")] 47 | let host = "http://localhost:8899"; 48 | 49 | let config_file = solana_cli_config::CONFIG_FILE.as_ref().unwrap().as_str(); 50 | let config = solana_cli_config::Config::load(config_file).unwrap(); 51 | let payer = read_keypair_file(&config.keypair_path).unwrap(); 52 | Client::new(payer, host.into()) 53 | } 54 | 55 | pub fn print_explorer_link(address: Pubkey, label: String) -> ClientResult<()> { 56 | println!( 57 | "{}: https://explorer.solana.com/address/{}", 58 | label.to_string(), 59 | address, 60 | ); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[derive(Debug)] 66 | pub struct WhirlpoolParams { 67 | pub whirlpool: Pubkey, 68 | pub token_mint_a: Pubkey, 69 | pub token_mint_b: Pubkey, 70 | pub oracle: Pubkey, 71 | } 72 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/crates/whirlpool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "whirlpool" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = ["cpi"] 8 | no-entrypoint = [] 9 | no-idl = [] 10 | no-log-ix-name = [] 11 | cpi = ["no-entrypoint"] 12 | 13 | [dependencies] 14 | anchor-lang = "0.26.0" 15 | anchor-gen = { git = "https://github.com/eliascm17/anchor-gen", version = "^0.3.1", features = ["compat-program-result"] } -------------------------------------------------------------------------------- /orca_whirlpool_dca/crates/whirlpool/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | 3 | use anchor_lang::solana_program::entrypoint::ProgramResult; 4 | 5 | anchor_gen::generate_cpi_interface!( 6 | idl_path = "idl.json", 7 | zero_copy(TickArray, Tick), 8 | packed(TickArray, Tick) 9 | ); 10 | 11 | impl Default for state::TickArray { 12 | fn default() -> Self { 13 | Self { 14 | start_tick_index: Default::default(), 15 | ticks: [Default::default(); 88], 16 | whirlpool: Default::default(), 17 | } 18 | } 19 | } 20 | 21 | anchor_lang::prelude::declare_id!("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"); 22 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/crates/whirlpool/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::Pubkey; 2 | 3 | pub const MAX_TICK_INDEX: i32 = 443636; 4 | pub const MIN_TICK_INDEX: i32 = -443636; 5 | pub const TICK_ARRAY_SIZE: i32 = 88; 6 | 7 | pub fn get_tick_array_pubkeys( 8 | tick_current_index: i32, 9 | tick_spacing: u16, 10 | a_to_b: bool, 11 | program_id: &Pubkey, 12 | whirlpool_pubkey: &Pubkey, 13 | ) -> [Pubkey; 3] { 14 | let mut offset = 0; 15 | let mut pubkeys: [Pubkey; 3] = Default::default(); 16 | 17 | for i in 0..pubkeys.len() { 18 | let start_tick_index = get_start_tick_index(tick_current_index, tick_spacing, offset); 19 | let tick_array_pubkey = 20 | get_tick_array_pubkey(program_id, whirlpool_pubkey, start_tick_index); 21 | pubkeys[i] = tick_array_pubkey; 22 | offset = if a_to_b { offset - 1 } else { offset + 1 }; 23 | } 24 | 25 | pubkeys 26 | } 27 | 28 | fn get_start_tick_index(tick_current_index: i32, tick_spacing: u16, offset: i32) -> i32 { 29 | let ticks_in_array = TICK_ARRAY_SIZE * tick_spacing as i32; 30 | let real_index = div_floor(tick_current_index, ticks_in_array); 31 | let start_tick_index = (real_index + offset) * ticks_in_array; 32 | 33 | assert!(MIN_TICK_INDEX <= start_tick_index); 34 | assert!(start_tick_index + ticks_in_array <= MAX_TICK_INDEX); 35 | start_tick_index 36 | } 37 | 38 | fn get_tick_array_pubkey( 39 | program_id: &Pubkey, 40 | whirlpool_pubkey: &Pubkey, 41 | start_tick_index: i32, 42 | ) -> Pubkey { 43 | Pubkey::find_program_address( 44 | &[ 45 | b"tick_array", 46 | whirlpool_pubkey.as_ref(), 47 | start_tick_index.to_string().as_bytes(), 48 | ], 49 | program_id, 50 | ) 51 | .0 52 | } 53 | 54 | fn div_floor(a: i32, b: i32) -> i32 { 55 | if a < 0 && a % b != 0 { 56 | a / b - 1 57 | } else { 58 | a / b 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orca-whirlpool-dca" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "orca_whirlpool_dca" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | delete = [] 15 | no-log-ix-name = [] 16 | cpi = ["no-entrypoint"] 17 | default = [] 18 | 19 | [dependencies] 20 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 21 | anchor-spl = "0.26.0" 22 | clockwork-sdk = "1.4.2" 23 | clockwork-macros = "1.4.0" 24 | whirlpool = { path = "../../crates/whirlpool" } -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("FMQ5m4PXuEBsN7fhxUXEfWJd8ywPMrg9AeZGgjHNLucP"); 4 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/instructions/dca_delete.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct DcaDelete<'info> { 5 | /// The authority (owner) of the dca. 6 | #[account()] 7 | pub authority: Signer<'info>, 8 | 9 | /// The address to return the data rent lamports to. 10 | #[account(mut)] 11 | pub close_to: SystemAccount<'info>, 12 | 13 | #[account( 14 | mut, 15 | seeds = [ 16 | SEED_DCA, 17 | dca.authority.as_ref(), 18 | dca.a_mint.as_ref(), 19 | dca.b_mint.as_ref(), 20 | ], 21 | bump, 22 | has_one = authority, 23 | close = close_to 24 | )] 25 | pub dca: Account<'info, Dca>, 26 | } 27 | 28 | pub fn handler(_ctx: Context) -> Result<()> { 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/instructions/dca_update.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::system_program}, 4 | }; 5 | 6 | #[derive(Accounts)] 7 | #[instruction(settings: DcaSettings)] 8 | pub struct DcaUpdate<'info> { 9 | #[account( 10 | mut, 11 | seeds = [ 12 | SEED_DCA, 13 | dca.authority.as_ref(), 14 | dca.a_mint.as_ref(), 15 | dca.b_mint.as_ref(), 16 | ], 17 | bump, 18 | )] 19 | pub dca: Account<'info, Dca>, 20 | 21 | #[account(mut)] 22 | pub payer: Signer<'info>, 23 | 24 | #[account(address = system_program::ID)] 25 | pub system_program: Program<'info, System>, 26 | } 27 | 28 | pub fn handler<'info>(ctx: Context>, settings: DcaSettings) -> Result<()> { 29 | let dca = &mut ctx.accounts.dca; 30 | 31 | if let Some(a) = settings.amount { 32 | dca.amount = a; 33 | } 34 | 35 | if let Some(oat) = settings.other_amount_threshold { 36 | dca.other_amount_threshold = oat; 37 | } 38 | 39 | if let Some(spl) = settings.sqrt_price_limit { 40 | dca.sqrt_price_limit = spl; 41 | } 42 | 43 | if let Some(asii) = settings.amount_specified_is_input { 44 | dca.amount_specified_is_input = asii; 45 | } 46 | 47 | if let Some(a2b) = settings.a_to_b { 48 | dca.a_to_b = a2b; 49 | } 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dca_create; 2 | pub mod dca_delete; 3 | pub mod dca_update; 4 | pub mod get_tick_arrays; 5 | pub mod swap; 6 | 7 | pub use dca_create::*; 8 | pub use dca_delete::*; 9 | pub use dca_update::*; 10 | pub use get_tick_arrays::*; 11 | pub use swap::*; 12 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod orca_whirlpool_dca { 13 | use super::*; 14 | 15 | /* 16 | * initialize dca account with orca swap params 17 | */ 18 | pub fn dca_create<'info>( 19 | ctx: Context<'_, '_, '_, 'info, DcaCreate<'info>>, 20 | amount: u64, 21 | other_amount_threshold: u64, 22 | sqrt_price_limit: u128, 23 | amount_specified_is_input: bool, 24 | a_to_b: bool, 25 | ) -> Result<()> { 26 | dca_create::handler( 27 | ctx, 28 | amount, 29 | other_amount_threshold, 30 | sqrt_price_limit, 31 | amount_specified_is_input, 32 | a_to_b, 33 | ) 34 | } 35 | 36 | /* 37 | * update dca settings 38 | */ 39 | pub fn dca_update<'info>( 40 | ctx: Context<'_, '_, '_, 'info, DcaUpdate<'info>>, 41 | settings: crate::state::DcaSettings, 42 | ) -> Result<()> { 43 | dca_update::handler(ctx, settings) 44 | } 45 | 46 | /* 47 | * delete dca account 48 | */ 49 | pub fn dca_delete<'info>(ctx: Context<'_, '_, '_, 'info, DcaDelete<'info>>) -> Result<()> { 50 | dca_delete::handler(ctx) 51 | } 52 | 53 | /* 54 | * get tick arrays for upcoming swap 55 | */ 56 | pub fn get_tick_arrays<'info>( 57 | ctx: Context>, 58 | ) -> Result { 59 | get_tick_arrays::handler(ctx) 60 | } 61 | 62 | /* 63 | * swap on orca whirlpool 64 | */ 65 | pub fn swap<'info>(ctx: Context<'_, '_, '_, 'info, Swap<'info>>) -> Result<()> { 66 | swap::handler(ctx) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/state/dca.rs: -------------------------------------------------------------------------------- 1 | use {anchor_lang::prelude::*, clockwork_macros::TryFromData, std::convert::TryFrom}; 2 | 3 | pub const SEED_DCA: &[u8] = b"dca"; 4 | 5 | /** 6 | * Dca 7 | */ 8 | 9 | #[account] 10 | #[derive(Debug, TryFromData)] 11 | pub struct Dca { 12 | pub authority: Pubkey, 13 | pub whirlpool: Pubkey, 14 | pub a_mint: Pubkey, 15 | pub b_mint: Pubkey, 16 | pub amount: u64, 17 | pub other_amount_threshold: u64, 18 | pub sqrt_price_limit: u128, 19 | pub amount_specified_is_input: bool, 20 | pub a_to_b: bool, 21 | } 22 | 23 | impl Dca { 24 | pub fn pubkey(authority: Pubkey, a_mint: Pubkey, b_mint: Pubkey) -> Pubkey { 25 | Pubkey::find_program_address( 26 | &[ 27 | SEED_DCA, 28 | authority.as_ref(), 29 | a_mint.as_ref(), 30 | b_mint.as_ref(), 31 | ], 32 | &crate::ID, 33 | ) 34 | .0 35 | } 36 | } 37 | 38 | /** 39 | * DCAAccount 40 | */ 41 | 42 | pub trait DcaAccount { 43 | fn new( 44 | &mut self, 45 | authority: Pubkey, 46 | whirlpool: Pubkey, 47 | a_mint: Pubkey, 48 | b_mint: Pubkey, 49 | amount: u64, 50 | other_amount_threshold: u64, 51 | sqrt_price_limit: u128, 52 | amount_specified_is_input: bool, 53 | a_to_b: bool, 54 | ) -> Result<()>; 55 | } 56 | 57 | impl DcaAccount for Account<'_, Dca> { 58 | fn new( 59 | &mut self, 60 | authority: Pubkey, 61 | whirlpool: Pubkey, 62 | a_mint: Pubkey, 63 | b_mint: Pubkey, 64 | amount: u64, 65 | other_amount_threshold: u64, 66 | sqrt_price_limit: u128, 67 | amount_specified_is_input: bool, 68 | a_to_b: bool, 69 | ) -> Result<()> { 70 | self.authority = authority; 71 | self.whirlpool = whirlpool; 72 | self.a_mint = a_mint; 73 | self.b_mint = b_mint; 74 | self.amount = amount; 75 | self.other_amount_threshold = other_amount_threshold; 76 | self.sqrt_price_limit = sqrt_price_limit; 77 | self.amount_specified_is_input = amount_specified_is_input; 78 | self.a_to_b = a_to_b; 79 | Ok(()) 80 | } 81 | } 82 | 83 | #[derive(AnchorSerialize, AnchorDeserialize)] 84 | pub struct DcaSettings { 85 | pub amount: Option, 86 | pub other_amount_threshold: Option, 87 | pub sqrt_price_limit: Option, 88 | pub amount_specified_is_input: Option, 89 | pub a_to_b: Option, 90 | } 91 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/programs/orca_whirlpool_dca/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod dca; 2 | 3 | pub use dca::*; 4 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/tests/orca_whirlpool_dca.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { OrcaWhirlpoolDca } from "../target/types/orca_whirlpool_dca"; 4 | 5 | describe("orca_whirlpool_dca", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.OrcaWhirlpoolDca as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /orca_whirlpool_dca/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /payments/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /payments/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /payments/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | payments = "CRrGhybmJf2ZDuFkiKqtu1y73uGTfFA77qVawveAQiP3" 7 | 8 | [programs.devnet] 9 | payments = "CRrGhybmJf2ZDuFkiKqtu1y73uGTfFA77qVawveAQiP3" 10 | 11 | [registry] 12 | url = "https://api.apr.dev" 13 | 14 | [provider] 15 | cluster = "devnet" 16 | wallet = "~/.config/solana/id.json" 17 | 18 | [scripts] 19 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 20 | -------------------------------------------------------------------------------- /payments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /payments/README.md: -------------------------------------------------------------------------------- 1 | # **Payments Program (Token Transfer)** 2 | 3 | This program creates a Thread that streams payment to a recipient. 4 | - First, we create a payment account and delegate the token transfer authority to the payment account. 5 | - Then, we schedule a Thread to disburse tokens using the payment account's information. 6 | 7 | --- 8 | 9 | ## Workflow 10 | **0. Install and run the Clockwork CLI** 11 | ```bash 12 | cargo install -f --locked clockwork-cli 13 | clockwork localnet 14 | ``` 15 | 16 | **1. Program Side - Deploying the Handler Program** 17 | 18 | We start by defining an instruction to execute, that is the __"WHAT"__: 19 | - We have already prepared a handler. 20 | - You just have to deploy it using `./deploy.sh localnet` _(nothing fancy, the usual program ids and network switch)_. 21 | 22 | **2. Client Side - Creating a Thread** 23 | 24 | Time to switch perspective, we need to do some work off-chain now, we will create a Thread, that's the __"HOW"__: 25 | - Check the `tests` folder, we are using anchor tests as a client. 26 | - Run `anchor test --skip-local-validator`, this will create a __Thread__ that listens for a certain account and print logs whenever the 27 | account is updated. 28 | 29 | ## How do I know if it works? 30 | Let's see how we can observe our newly created Thread: 31 | - The prepared client, will also print the Solana Explorer url 32 | ```bash 33 | initialize tx: ✅ https://explorer.solana.com/tx/... 34 | ping tx: ✅ https://explorer.solana.com/tx/... 35 | ... 36 | ``` 37 | - If you have the Clockwork Cli installed, you can use the `clockwork` command 38 | ```bash 39 | clockwork thread get --address 40 | clockwork thread get 41 | ``` 42 | 43 | ## How do I know if it works? 44 | Let's see how we can observe our newly created Thread: 45 | - Run the tests! 46 | ```bash 47 | anchor test --skip-local-validator 48 | ``` 49 | - If you have the Clockwork Cli installed, you can use the `clockwork` command 50 | ```bash 51 | clockwork thread get --address 52 | clockwork thread get 53 | ``` 54 | 55 | --- 56 | 57 | ## Common Errors 58 | Please refer to the [FAQ](https://github.com/clockwork-xyz/docs/blob/main/FAQ.md#common-errors). 59 | 60 | ## Questions 61 | Come build with us and ask us questions [Discord](https://discord.gg/epHsTsnUre)! 62 | -------------------------------------------------------------------------------- /payments/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /payments/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /payments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@clockwork-xyz/sdk": "^0.2.3", 8 | "@project-serum/anchor": "^0.26.0", 9 | "@solana/spl-token": "^0.2.0" 10 | }, 11 | "devDependencies": { 12 | "@types/bn.js": "^5.1.0", 13 | "@types/chai": "^4.3.0", 14 | "@types/mocha": "^9.0.0", 15 | "chai": "^4.3.4", 16 | "mocha": "^9.0.3", 17 | "prettier": "^2.6.2", 18 | "ts-mocha": "^10.0.0", 19 | "typescript": "^4.3.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /payments/programs/payments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "payments" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "payments" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 20 | anchor-spl = { version = "0.26.0", features = ["token"] } 21 | clockwork-sdk = { version = "~2.0.1" } 22 | -------------------------------------------------------------------------------- /payments/programs/payments/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /payments/programs/payments/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("CRrGhybmJf2ZDuFkiKqtu1y73uGTfFA77qVawveAQiP3"); 4 | -------------------------------------------------------------------------------- /payments/programs/payments/src/instructions/create_payment.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{ 4 | prelude::*, 5 | solana_program::{system_program, sysvar}, 6 | }, 7 | anchor_spl::{ 8 | associated_token::AssociatedToken, 9 | token::{self, Mint, TokenAccount}, 10 | }, 11 | std::mem::size_of, 12 | }; 13 | 14 | #[derive(Accounts)] 15 | #[instruction(amount: u64)] 16 | pub struct CreatePayment<'info> { 17 | #[account(address = anchor_spl::associated_token::ID)] 18 | pub associated_token_program: Program<'info, AssociatedToken>, 19 | 20 | #[account(mut)] 21 | pub authority: Signer<'info>, 22 | 23 | #[account( 24 | mut, 25 | associated_token::authority = authority, 26 | associated_token::mint = mint, 27 | )] 28 | pub authority_token_account: Account<'info, TokenAccount>, 29 | 30 | #[account()] 31 | pub mint: Account<'info, Mint>, 32 | 33 | #[account( 34 | init, 35 | payer = authority, 36 | seeds = [ 37 | SEED_PAYMENT, 38 | authority.key().as_ref(), 39 | mint.key().as_ref(), 40 | recipient.key().as_ref(), 41 | ], 42 | bump, 43 | space = 8 + size_of::(), 44 | )] 45 | pub payment: Account<'info, Payment>, 46 | 47 | /// CHECK: the recipient is validated by the seeds of the payment account 48 | #[account()] 49 | pub recipient: AccountInfo<'info>, 50 | 51 | #[account(address = sysvar::rent::ID)] 52 | pub rent: Sysvar<'info, Rent>, 53 | 54 | #[account(address = system_program::ID)] 55 | pub system_program: Program<'info, System>, 56 | 57 | #[account(address = anchor_spl::token::ID)] 58 | pub token_program: Program<'info, token::Token>, 59 | } 60 | 61 | pub fn handler<'info>(ctx: Context, amount: u64) -> Result<()> { 62 | // Get accounts. 63 | let authority = &ctx.accounts.authority; 64 | let authority_token_account = &mut ctx.accounts.authority_token_account; 65 | let mint = &ctx.accounts.mint; 66 | let payment = &mut ctx.accounts.payment; 67 | let recipient = &ctx.accounts.recipient; 68 | let token_program = &ctx.accounts.token_program; 69 | 70 | // Initialize the payment account. 71 | payment.new( 72 | amount, 73 | authority.key(), 74 | mint.key(), 75 | recipient.key(), 76 | )?; 77 | 78 | // Approve the payment pda to spend from the authority's token account. 79 | token::approve( 80 | CpiContext::new( 81 | token_program.to_account_info(), 82 | token::Approve { 83 | authority: authority.to_account_info(), 84 | to: authority_token_account.to_account_info(), 85 | delegate: payment.to_account_info(), 86 | }), 87 | u64::MAX 88 | )?; 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /payments/programs/payments/src/instructions/disburse_payment.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::{system_program, sysvar}}, 4 | anchor_spl::{ 5 | associated_token::AssociatedToken, 6 | token::{self, Mint, TokenAccount, Transfer} 7 | }, 8 | clockwork_sdk::{ 9 | state::{Thread, ThreadAccount, ThreadResponse}, 10 | }, 11 | }; 12 | 13 | #[derive(Accounts)] 14 | pub struct DisbursePayment<'info> { 15 | #[account(address = anchor_spl::associated_token::ID)] 16 | pub associated_token_program: Program<'info, AssociatedToken>, 17 | 18 | /// CHECK: The authority is validated by the payment account 19 | #[account(address = payment.authority)] 20 | pub authority: AccountInfo<'info>, 21 | 22 | #[account( 23 | mut, 24 | associated_token::authority = authority, 25 | associated_token::mint = mint, 26 | )] 27 | pub authority_token_account: Account<'info, TokenAccount>, 28 | 29 | #[account(address = payment.mint)] 30 | pub mint: Box>, 31 | 32 | #[account(mut)] 33 | pub payer: Signer<'info>, 34 | 35 | #[account( 36 | mut, 37 | seeds = [ 38 | SEED_PAYMENT, 39 | payment.authority.as_ref(), 40 | payment.mint.as_ref(), 41 | payment.recipient.as_ref(), 42 | ], 43 | bump, 44 | has_one = authority, 45 | has_one = mint, 46 | has_one = recipient, 47 | )] 48 | pub payment: Box>, 49 | 50 | #[account( 51 | signer, 52 | address = thread.pubkey(), 53 | constraint = thread.authority.eq(&payment.authority), 54 | )] 55 | pub thread: Box>, 56 | 57 | /// CHECK: The recipient is validated by the payment account 58 | #[account(address = payment.recipient)] 59 | pub recipient: AccountInfo<'info>, 60 | 61 | #[account( 62 | init_if_needed, 63 | payer = payer, 64 | associated_token::authority = recipient, 65 | associated_token::mint = mint, 66 | )] 67 | pub recipient_token_account: Box>, 68 | 69 | #[account(address = sysvar::rent::ID)] 70 | pub rent: Sysvar<'info, Rent>, 71 | 72 | #[account(address = system_program::ID)] 73 | pub system_program: Program<'info, System>, 74 | 75 | #[account(address = anchor_spl::token::ID)] 76 | pub token_program: Program<'info, anchor_spl::token::Token>, 77 | } 78 | 79 | pub fn handler(ctx: Context) -> Result { 80 | // Get accounts. 81 | let authority_token_account = &mut ctx.accounts.authority_token_account; 82 | let payment = &mut ctx.accounts.payment; 83 | let recipient_token_account = &ctx.accounts.recipient_token_account; 84 | let token_program = &ctx.accounts.token_program; 85 | 86 | // Transfer tokens from authority's ATA to recipient's ATA. 87 | let bump = *ctx.bumps.get("payment").unwrap(); 88 | token::transfer( 89 | CpiContext::new_with_signer( 90 | token_program.to_account_info(), 91 | Transfer { 92 | from: authority_token_account.to_account_info(), 93 | to: recipient_token_account.to_account_info(), 94 | authority: payment.to_account_info(), 95 | }, 96 | &[&[ 97 | SEED_PAYMENT, 98 | payment.authority.as_ref(), 99 | payment.mint.as_ref(), 100 | payment.recipient.as_ref(), 101 | &[bump]] 102 | ]), 103 | payment.amount, 104 | )?; 105 | 106 | Ok(ThreadResponse::default()) 107 | } 108 | -------------------------------------------------------------------------------- /payments/programs/payments/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_payment; 2 | pub mod disburse_payment; 3 | pub mod update_payment; 4 | 5 | pub use create_payment::*; 6 | pub use disburse_payment::*; 7 | pub use update_payment::*; 8 | -------------------------------------------------------------------------------- /payments/programs/payments/src/instructions/update_payment.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | #[instruction(amount: Option)] 5 | pub struct UpdatePayment<'info> { 6 | #[account(mut)] 7 | pub authority: Signer<'info>, 8 | 9 | #[account( 10 | mut, 11 | seeds = [ 12 | SEED_PAYMENT, 13 | payment.authority.key().as_ref(), 14 | payment.mint.key().as_ref(), 15 | payment.recipient.key().as_ref(), 16 | ], 17 | bump, 18 | has_one = authority, 19 | )] 20 | pub payment: Account<'info, Payment>, 21 | } 22 | 23 | pub fn handler<'info>(ctx: Context, amount: Option) -> Result<()> { 24 | // Get accounts 25 | let payment = &mut ctx.accounts.payment; 26 | 27 | // Update the payment amount. 28 | if let Some(amount) = amount { 29 | payment.amount = amount; 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /payments/programs/payments/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod payments { 13 | use super::*; 14 | 15 | /* 16 | * Create Payment Approval 17 | */ 18 | pub fn create_payment(ctx: Context, amount: u64) -> Result<()> { 19 | create_payment::handler(ctx, amount) 20 | } 21 | 22 | /* 23 | * disburse payment from program authority's ATA to recipient's ATA 24 | */ 25 | pub fn disburse_payment(ctx: Context) -> 26 | Result { 27 | disburse_payment::handler(ctx) 28 | } 29 | 30 | /* 31 | * update disbursement amount 32 | */ 33 | pub fn update_payment(ctx: Context, amount: Option) -> Result<()> { 34 | update_payment::handler(ctx, amount) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /payments/programs/payments/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod payment; 2 | 3 | pub use payment::*; 4 | -------------------------------------------------------------------------------- /payments/programs/payments/src/state/payment.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_PAYMENT: &[u8] = b"payment"; 7 | 8 | /** 9 | * Payment 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Payment { 15 | pub amount: u64, 16 | pub authority: Pubkey, 17 | pub mint: Pubkey, 18 | pub recipient: Pubkey, 19 | } 20 | 21 | impl Payment { 22 | pub fn pubkey(authority: Pubkey, mint: Pubkey, recipient: Pubkey) -> Pubkey { 23 | Pubkey::find_program_address( 24 | &[ 25 | SEED_PAYMENT, 26 | authority.as_ref(), 27 | mint.as_ref(), 28 | recipient.as_ref(), 29 | ], 30 | &crate::ID, 31 | ) 32 | .0 33 | } 34 | } 35 | 36 | impl TryFrom> for Payment { 37 | type Error = Error; 38 | fn try_from(data: Vec) -> std::result::Result { 39 | Payment::try_deserialize(&mut data.as_slice()) 40 | } 41 | } 42 | 43 | pub trait PaymentAccount { 44 | fn new( 45 | &mut self, 46 | amount: u64, 47 | authority: Pubkey, 48 | mint: Pubkey, 49 | recipient: Pubkey, 50 | ) -> Result<()>; 51 | } 52 | 53 | impl PaymentAccount for Account<'_, Payment> { 54 | fn new( 55 | &mut self, 56 | amount: u64, 57 | authority: Pubkey, 58 | mint: Pubkey, 59 | recipient: Pubkey, 60 | ) -> Result<()> { 61 | self.amount = amount; 62 | self.authority = authority; 63 | self.mint = mint; 64 | self.recipient = recipient; 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /payments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pyth-trigger-test/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /pyth-trigger-test/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /pyth-trigger-test/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | pyth_trigger_test = "3D9Z2VywLehXGJxMDBdt7gVhowTWHc2LNqtfRNcp2g5P" 7 | 8 | [programs.devnet] 9 | pyth_trigger_test = "3D9Z2VywLehXGJxMDBdt7gVhowTWHc2LNqtfRNcp2g5P" 10 | 11 | [registry] 12 | url = "https://api.apr.dev" 13 | 14 | [provider] 15 | cluster = "devnet" 16 | wallet = "/home/ubuntu/.config/solana/id.json" 17 | 18 | [scripts] 19 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 20 | -------------------------------------------------------------------------------- /pyth-trigger-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | # [profile.release] 7 | # overflow-checks = true 8 | # lto = "fat" 9 | # codegen-units = 1 10 | 11 | # [profile.release.build-override] 12 | # opt-level = 3 13 | # incremental = false 14 | # codegen-units = 1 15 | -------------------------------------------------------------------------------- /pyth-trigger-test/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /pyth-trigger-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@clockwork-xyz/sdk": "^0.3.0", 8 | "@coral-xyz/anchor": "^0.27.0" 9 | }, 10 | "devDependencies": { 11 | "@types/bn.js": "^5.1.0", 12 | "@types/chai": "^4.3.0", 13 | "@types/mocha": "^9.0.0", 14 | "chai": "^4.3.4", 15 | "mocha": "^9.0.3", 16 | "prettier": "^2.6.2", 17 | "ts-mocha": "^10.0.0", 18 | "typescript": "^4.3.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pyth-trigger-test/programs/pyth-trigger-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth-trigger-test" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "pyth_trigger_test" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "0.27.0" 20 | clockwork-sdk = { git = "https://github.com/clockwork-xyz/clockwork", branch = "nick/pyth-trigger" } 21 | pyth-sdk-solana = "0.7.1" 22 | -------------------------------------------------------------------------------- /pyth-trigger-test/programs/pyth-trigger-test/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /pyth-trigger-test/tests/pyth-trigger-test.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program } from "@coral-xyz/anchor"; 3 | import { PublicKey, SystemProgram } from "@solana/web3.js"; 4 | import { PythTriggerTest } from "../target/types/pyth_trigger_test"; 5 | import { ClockworkProvider } from "@clockwork-xyz/sdk"; 6 | 7 | const provider = anchor.AnchorProvider.env(); 8 | anchor.setProvider(provider); 9 | const wallet = provider.wallet; 10 | // const program = anchor.workspace.Counter as Program; 11 | const program = anchor.workspace.PythTriggerTest as Program; 12 | const clockworkProvider = ClockworkProvider.fromAnchorProvider(provider); 13 | 14 | 15 | describe("pyth-trigger-test", () => { 16 | // Configure the client to use the local cluster. 17 | // anchor.setProvider(anchor.AnchorProvider.env()); 18 | // const wallet = provider.wallet; 19 | // const program = anchor.workspace.PythTriggerTest as Program; 20 | // const clockworkProvider = ClockworkProvider.fromAnchorProvider(provider); 21 | 22 | it("Is initialized!", async () => { 23 | const threadId = ""; 24 | const [threadAuthority] = PublicKey.findProgramAddressSync( 25 | [anchor.utils.bytes.utf8.encode("authority")], // 👈 make sure it matches on the prog side 26 | program.programId 27 | ); 28 | const [threadAddress, threadBump] = clockworkProvider.getThreadPDA(threadAuthority, threadId) 29 | 30 | // Add your test here. 31 | const tx = await program.methods 32 | .initialize() 33 | .accounts({ 34 | payer: wallet.publicKey, 35 | clockworkProgram: clockworkProvider.threadProgram.programId, 36 | systemProgram: SystemProgram.programId, 37 | threadAuthority: threadAuthority, 38 | thread: threadAddress, 39 | }) 40 | .rpc(); 41 | 42 | console.log("Your transaction signature", tx); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /pyth-trigger-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pyth_feed/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /pyth_feed/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /pyth_feed/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.devnet] 6 | pyth_feed = "7PcMb2uYgatpWL3uhY4jXG7V4Jr9TUyUNTeRzMyFGwFg" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "devnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | 18 | # mainnet SOL/USD price feed: https://pyth.network/price-feeds/crypto-sol-usd?cluster=mainnet-beta 19 | #[test.validator] 20 | #url = "https://api.mainnet-beta.solana.com" 21 | #[[test.validator.clone]] 22 | #address = "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG" 23 | -------------------------------------------------------------------------------- /pyth_feed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "client", 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /pyth_feed/README.md: -------------------------------------------------------------------------------- 1 | # **Pyth Feed Program** 2 | 3 | ## Workflow 4 | **0. Install and run the Clockwork CLI** 5 | ```bash 6 | cargo install -f --locked clockwork-cli 7 | clockwork localnet 8 | ``` 9 | 10 | **1. Program Side - Deploying the Handler Program** 11 | 12 | We start by defining an instruction to execute, that is the __"WHAT"__: 13 | - We have already prepared a handler. 14 | - You just have to deploy it using `./deploy.sh localnet` _(nothing fancy, the usual program ids and network switch)_. 15 | 16 | **2. Client Side - Creating a Thread** 17 | 18 | Time to switch perspective, we need to do some work off-chain now, we will create a Thread, that's the __"HOW"__: 19 | - Check the `tests` folder, we are using anchor tests as a client. 20 | - Run `anchor test --skip-local-validator`, this will create a __Thread__ that listens for a certain account and print logs whenever the 21 | account is updated. 22 | 23 | ## How do I know if it works? 24 | Let's see how we can observe our newly created Thread: 25 | - The prepared client, will also print the Solana Explorer url 26 | ```bash 27 | initialize tx: ✅ https://explorer.solana.com/tx/... 28 | ping tx: ✅ https://explorer.solana.com/tx/... 29 | ... 30 | ``` 31 | - If you have the Clockwork Cli installed, you can use the `clockwork` command 32 | ```bash 33 | clockwork thread get --address 34 | clockwork thread get 35 | ``` 36 | --- 37 | 38 | ## Common Errors 39 | Please refer to the [FAQ](https://github.com/clockwork-xyz/docs/blob/main/FAQ.md#common-errors). 40 | 41 | ## Questions 42 | Come build with us and ask us questions [Discord](https://discord.gg/epHsTsnUre)! 43 | -------------------------------------------------------------------------------- /pyth_feed/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | pyth_feed = { path = "../programs/pyth_feed", features = ["no-entrypoint"], version = "0.1.0" } 9 | anchor-lang = "0.26.0" 10 | clockwork-client = { version = "1.4.2" } 11 | clockwork-utils = { version = "1.4.2" } 12 | solana-cli-config = "1.10.4" 13 | solana-sdk = "1.14.12" 14 | 15 | [features] 16 | localnet = [] 17 | -------------------------------------------------------------------------------- /pyth_feed/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /pyth_feed/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /pyth_feed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth_feed" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "pyth_feed" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } 20 | clockwork-sdk = { version = "1.4.2" } 21 | pyth-sdk-solana = "0.7.0" 22 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("7PcMb2uYgatpWL3uhY4jXG7V4Jr9TUyUNTeRzMyFGwFg"); 4 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_feed; 2 | pub mod process_feed; 3 | 4 | pub use create_feed::*; 5 | pub use process_feed::*; 6 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/instructions/process_feed.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::prelude::*, 4 | clockwork_sdk::state::{Thread, ThreadAccount}, 5 | pyth_sdk_solana::load_price_feed_from_account_info, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | pub struct ProcessFeed<'info> { 10 | #[account(mut, seeds = [SEED_FEED, feed.authority.as_ref()], bump)] 11 | pub feed: Account<'info, Feed>, 12 | 13 | /// CHECK: this account is manually being checked against the feed account's feed field 14 | #[account( 15 | constraint = pyth_price_feed.key() == feed.pyth_price_feed 16 | )] 17 | pub pyth_price_feed: AccountInfo<'info>, 18 | 19 | #[account( 20 | address = thread.pubkey(), 21 | constraint = thread.id.eq("feed"), 22 | signer, 23 | constraint = thread.authority == feed.key() 24 | )] 25 | pub thread: Account<'info, Thread>, 26 | } 27 | 28 | pub fn handler<'info>(ctx: Context) -> Result<()> { 29 | let feed = &mut ctx.accounts.feed; 30 | let pyth_data_feed = &ctx.accounts.pyth_price_feed; 31 | 32 | // load price feed 33 | let price_feed = load_price_feed_from_account_info(&pyth_data_feed.to_account_info()).unwrap(); 34 | let price = price_feed.get_price_unchecked(); 35 | 36 | // update last publish time 37 | feed.publish_time = price.publish_time; 38 | 39 | let current_timestamp1 = Clock::get()?.unix_timestamp; 40 | match price_feed.get_price_no_older_than(current_timestamp1, 60) { 41 | Some(price) => { 42 | msg!( 43 | "Price change for {}: {}", 44 | price_feed.id.to_string(), 45 | price.price, 46 | ); 47 | } 48 | None => {} 49 | } 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use instructions::*; 10 | 11 | #[program] 12 | pub mod pyth_feed { 13 | use super::*; 14 | 15 | pub fn create_feed<'info>(ctx: Context<'_, '_, '_, 'info, CreateFeed<'info>>) -> Result<()> { 16 | create_feed::handler(ctx) 17 | } 18 | 19 | pub fn process_feed(ctx: Context) -> Result<()> { 20 | process_feed::handler(ctx) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/state/feed.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, AnchorDeserialize}; 2 | 3 | pub const SEED_FEED: &[u8] = b"feed"; 4 | 5 | /** 6 | * Feed 7 | */ 8 | 9 | #[account] 10 | #[derive(Debug)] 11 | pub struct Feed { 12 | pub authority: Pubkey, 13 | pub pyth_price_feed: Pubkey, 14 | pub publish_time: i64, 15 | } 16 | 17 | impl Feed { 18 | pub fn pubkey(authority: Pubkey) -> Pubkey { 19 | Pubkey::find_program_address(&[SEED_FEED, authority.key().as_ref()], &crate::ID).0 20 | } 21 | } 22 | 23 | impl TryFrom> for Feed { 24 | type Error = Error; 25 | fn try_from(data: Vec) -> std::result::Result { 26 | Feed::try_deserialize(&mut data.as_slice()) 27 | } 28 | } 29 | 30 | /** 31 | * FeedAccount 32 | */ 33 | 34 | pub trait FeedAccount { 35 | fn new(&mut self, authority: Pubkey, pyth_price_feed: Pubkey) -> Result<()>; 36 | } 37 | 38 | impl FeedAccount for Account<'_, Feed> { 39 | fn new(&mut self, authority: Pubkey, pyth_price_feed: Pubkey) -> Result<()> { 40 | self.authority = authority; 41 | self.pyth_price_feed = pyth_price_feed; 42 | self.publish_time = 0; 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pyth_feed/programs/pyth_feed/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod feed; 2 | 3 | pub use feed::*; 4 | -------------------------------------------------------------------------------- /pyth_feed/tests/pyth_feed.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { PythFeed } from "../target/types/pyth_feed"; 4 | 5 | describe("pyth_feed", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.PythFeed as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /pyth_feed/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pyth_stats/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | -------------------------------------------------------------------------------- /pyth_stats/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /pyth_stats/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.mainnet] 6 | pyth_stats = "5cj44QGY53QhgiqWYaFMQuGtLieeR4gH6rJzCU9iLoAR" 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "mainnet" 13 | wallet = "~/.config/solana/id.json" 14 | 15 | [scripts] 16 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 17 | -------------------------------------------------------------------------------- /pyth_stats/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "client", 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 -------------------------------------------------------------------------------- /pyth_stats/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth_stats_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | anchor-lang = "0.26.0" 9 | clockwork-client = "1.4.0" 10 | pyth_stats = { path = "../programs/pyth_stats", features = ["no-entrypoint"], version = "0.1.0" } 11 | solana-sdk = "1.13.5" 12 | 13 | [features] 14 | mainnet = [] -------------------------------------------------------------------------------- /pyth_stats/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /pyth_stats/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /pyth_stats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth_stats" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "pyth_stats" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "0.26.0" 20 | bytemuck = "1.12.3" 21 | clockwork-sdk = "1.4.0" 22 | pyth-sdk-solana = "0.7.0" 23 | 24 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("5cj44QGY53QhgiqWYaFMQuGtLieeR4gH6rJzCU9iLoAR"); 4 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/instructions/initialize.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::system_program}, 4 | std::mem::size_of, 5 | }; 6 | 7 | static INITIAL_BUFFER_LIMIT: usize = 100; 8 | 9 | #[derive(Accounts)] 10 | #[instruction(lookback_window: i64)] 11 | pub struct Initialize<'info> { 12 | #[account( 13 | init, 14 | seeds = [ 15 | SEED_AVG_BUFFER, 16 | stat.key().as_ref(), 17 | ], 18 | bump, 19 | payer = signer, 20 | space = 8 + size_of::() + (INITIAL_BUFFER_LIMIT * size_of::()), 21 | )] 22 | pub avg_buffer: AccountLoader<'info, AvgBuffer>, 23 | 24 | #[account( 25 | init, 26 | seeds = [ 27 | SEED_PRICE_BUFFER, 28 | stat.key().as_ref(), 29 | ], 30 | bump, 31 | payer = signer, 32 | space = 8 + size_of::() + (INITIAL_BUFFER_LIMIT * size_of::()), 33 | )] 34 | pub price_buffer: AccountLoader<'info, PriceBuffer>, 35 | 36 | /// CHECK: this account should be a pyth feed account 37 | pub price_feed: AccountInfo<'info>, 38 | 39 | #[account(mut)] 40 | pub signer: Signer<'info>, 41 | 42 | #[account( 43 | init, 44 | seeds = [ 45 | SEED_STAT, 46 | price_feed.key().as_ref(), 47 | signer.key().as_ref(), 48 | &lookback_window.to_le_bytes(), 49 | ], 50 | bump, 51 | payer = signer, 52 | space = 8 + size_of::(), 53 | )] 54 | pub stat: Account<'info, Stat>, 55 | 56 | #[account(address = system_program::ID)] 57 | pub system_program: Program<'info, System>, 58 | 59 | #[account( 60 | init, 61 | seeds = [ 62 | SEED_TIME_SERIES, 63 | stat.key().as_ref(), 64 | ], 65 | bump, 66 | payer = signer, 67 | space = 8 + size_of::() + (INITIAL_BUFFER_LIMIT * size_of::()), 68 | )] 69 | pub time_series: AccountLoader<'info, TimeSeries>, 70 | } 71 | 72 | pub fn handler<'info>(ctx: Context>, lookback_window: i64) -> Result<()> { 73 | let mut _avg_buffer= ctx.accounts.avg_buffer.load_init()?; 74 | let mut _price_buffer= ctx.accounts.price_buffer.load_init()?; 75 | let price_feed = &ctx.accounts.price_feed; 76 | let signer = &ctx.accounts.signer; 77 | let stat = &mut ctx.accounts.stat; 78 | let mut _time_series = ctx.accounts.time_series.load_init()?; 79 | 80 | stat.new(price_feed.key(), signer.key(), lookback_window, INITIAL_BUFFER_LIMIT)?; 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod calc; 2 | pub mod initialize; 3 | pub mod realloc_buffers; 4 | 5 | pub use calc::*; 6 | pub use initialize::*; 7 | pub use realloc_buffers::*; 8 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod state; 3 | 4 | mod instructions; 5 | 6 | pub use id::ID; 7 | 8 | use anchor_lang::prelude::*; 9 | use clockwork_sdk::state::*; 10 | use instructions::*; 11 | 12 | #[program] 13 | pub mod stats { 14 | use super::*; 15 | 16 | pub fn initialize(ctx: Context, lookback_window: i64) -> Result<()> { 17 | initialize::handler(ctx, lookback_window) 18 | } 19 | pub fn calc(ctx: Context) -> Result { 20 | calc::handler(ctx) 21 | } 22 | 23 | pub fn realloc_buffers(ctx: Context) -> Result { 24 | realloc_buffers::handler(ctx) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/state/buffers.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | pub const SEED_TIME_SERIES: &[u8] = b"time_series"; 4 | 5 | /** 6 | * TimeSeries 7 | */ 8 | 9 | #[account(zero_copy)] 10 | pub struct TimeSeries {} 11 | 12 | impl TimeSeries { 13 | pub fn pubkey(stat: Pubkey) -> Pubkey { 14 | Pubkey::find_program_address(&[SEED_TIME_SERIES, stat.as_ref()], &crate::ID).0 15 | } 16 | } 17 | 18 | pub const SEED_PRICE_BUFFER: &[u8] = b"price_buffer"; 19 | 20 | /** 21 | * PriceBuffer 22 | */ 23 | 24 | #[account(zero_copy)] 25 | pub struct PriceBuffer {} 26 | 27 | impl PriceBuffer { 28 | pub fn pubkey(stat: Pubkey) -> Pubkey { 29 | Pubkey::find_program_address(&[SEED_PRICE_BUFFER, stat.as_ref()], &crate::ID).0 30 | } 31 | } 32 | 33 | pub const SEED_AVG_BUFFER: &[u8] = b"avg_buffer"; 34 | 35 | /** 36 | * AvgBuffer 37 | */ 38 | 39 | #[account(zero_copy)] 40 | pub struct AvgBuffer {} 41 | 42 | impl AvgBuffer { 43 | pub fn pubkey(stat: Pubkey) -> Pubkey { 44 | Pubkey::find_program_address(&[SEED_AVG_BUFFER, stat.as_ref()], &crate::ID).0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffers; 2 | pub mod stat; 3 | 4 | pub use buffers::*; 5 | pub use stat::*; 6 | -------------------------------------------------------------------------------- /pyth_stats/programs/pyth_stats/src/state/stat.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | pub const SEED_STAT: &[u8] = b"stat"; 4 | /** 5 | * Stat 6 | */ 7 | 8 | #[account()] 9 | pub struct Stat { 10 | pub price_feed: Pubkey, 11 | pub authority: Pubkey, 12 | pub lookback_window: i64, 13 | pub sample_count: usize, 14 | pub sample_sum: i64, 15 | pub buffer_size: usize, 16 | pub head: Option, 17 | } 18 | 19 | impl Stat { 20 | pub fn pubkey(price_feed: Pubkey, authority: Pubkey, lookback_window: i64) -> Pubkey { 21 | Pubkey::find_program_address( 22 | &[ 23 | SEED_STAT, 24 | price_feed.as_ref(), 25 | authority.as_ref(), 26 | &lookback_window.to_le_bytes(), 27 | ], 28 | &crate::ID, 29 | ) 30 | .0 31 | } 32 | } 33 | 34 | impl TryFrom> for Stat { 35 | type Error = Error; 36 | fn try_from(data: Vec) -> std::result::Result { 37 | Stat::try_deserialize(&mut data.as_slice()) 38 | } 39 | } 40 | 41 | /** 42 | * StatAccount 43 | */ 44 | 45 | impl Stat { 46 | pub fn new( 47 | &mut self, 48 | price_feed: Pubkey, 49 | authority: Pubkey, 50 | lookback_window: i64, 51 | buffer_size: usize, 52 | ) -> Result<()> { 53 | self.price_feed = price_feed; 54 | self.authority = authority; 55 | self.lookback_window = lookback_window; 56 | self.sample_count = 0; 57 | self.sample_sum = 0; 58 | self.buffer_size = buffer_size; 59 | self.head = None; 60 | Ok(()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pyth_stats/tests/pyth_stats.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { PythStats } from "../target/types/pyth_stats"; 4 | 5 | describe("pyth_stats", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.PythStats as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /pyth_stats/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scripts/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | here="$(dirname "$0")" 6 | cd "$here"/.. 7 | 8 | EXAMPLES=( 9 | distributor 10 | event_stream 11 | hello_clockwork 12 | openbook_crank 13 | openbook_dca 14 | payments 15 | pyth_feed 16 | pyth_stats 17 | # subscriptions not migrated to 1.4.2 yet 18 | ) 19 | 20 | for ex in "${EXAMPLES[@]}"; do 21 | cd "$ex" 22 | echo "building $ex" 23 | cargo build 24 | cd - 25 | done 26 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ $# -eq 0 ] || [ "$1" != "localnet" ] && [ "$1" != "devnet" ]; then 6 | echo "usage: $0 " 7 | exit 1 8 | fi 9 | 10 | crate_name=$(basename "$PWD") 11 | crate_name=$(sed -E "s/^([0-9]+)-//g" <<< "$crate_name") 12 | network=$1 13 | 14 | replace_in_file() { 15 | if [ "$(uname)" == "Darwin" ]; then 16 | sed -i '' -e "$1" "$2" 17 | else 18 | sed -i'' -e "$1" "$2" 19 | fi 20 | } 21 | 22 | # Get pubkey addresses 23 | # Delete old program ID if -f is passed 24 | if [ $# -eq 2 ] && [ "$2" == "-f" ]; then 25 | echo "Deleting old program id" 26 | rm -rf "target/deploy/$crate_name-keypair.json" 27 | fi 28 | anchor build 29 | 30 | program_id=$(solana address -k "target/deploy/$crate_name-keypair.json") 31 | echo "project_name: $crate_name" 32 | echo "program_id: $program_id" 33 | 34 | # Update program IDs 35 | replace_in_file 's/^declare_id!(".*");/declare_id!("'${program_id}'");/g' "programs/$crate_name/src/lib.rs" 36 | replace_in_file 's/^'${crate_name}' = ".*"/'${crate_name}' = "'${program_id}'"/g' Anchor.toml 37 | 38 | # Rebuild with new program ID 39 | anchor build 40 | 41 | deploy_devnet() { 42 | solana config set --url devnet 43 | replace_in_file 's/^cluster = ".*"/cluster = "'${network}'"/g' Anchor.toml 44 | anchor deploy 45 | } 46 | 47 | deploy_localnet() { 48 | solana config set --url "http://localhost:8899" 49 | replace_in_file 's/^cluster = ".*"/cluster = "'${network}'"/g' Anchor.toml 50 | anchor deploy 51 | } 52 | 53 | # Deploy 54 | if [ "$1" = "devnet" ]; then 55 | deploy_devnet 56 | else 57 | deploy_localnet 58 | fi 59 | 60 | -------------------------------------------------------------------------------- /scripts/update-clockwork-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | here="$(dirname "$0")" 6 | cd "$here"/.. 7 | 8 | 9 | # Get new Clockwork version 10 | current_version=$(cat CLOCKWORK_SDK_VERSION) 11 | echo "Current Clockwork version: $current_version" 12 | read -r -p " New Clockwork version: " new_version 13 | new_version=${new_version//[v]/''} 14 | 15 | 16 | # # Get new Anchor version 17 | # current_anchor_version=$(cat ANCHOR_VERSION) 18 | # echo "Current Anchor version: $current_anchor_version" 19 | # read -r -p " New Anchor version (or enter if unchanged): " new_anchor_version 20 | # new_anchor_version="${new_anchor_version:-$current_anchor_version}" 21 | 22 | 23 | # Get new Solana version 24 | current_solana_version=$(cat SOLANA_VERSION) 25 | echo "Current Solana version: $current_solana_version" 26 | read -r -p " New Solana version (or enter if unchanged): " new_solana_version 27 | new_solana_version="${new_solana_version:-$current_solana_version}" 28 | 29 | 30 | # # Update the root Cargo.tomls with patches (eventually remove this once we can deploy to crates normally) 31 | # echo "Updating patched versions" 32 | # # ROOT_TOMLS=$(find . -maxdepth 2 -type f -iname "Cargo.toml") 33 | # declare ROOT_TOMLS=() 34 | # while IFS='' read -r line; do ROOT_TOMLS+=("$line"); done < <(find . -maxdepth 2 -type f -iname "Cargo.toml" -not -path './pyth_feed/*') 35 | # for toml in "${ROOT_TOMLS[@]}"; do 36 | # sed -i '' -e 's/^anchor-lang =.*/anchor-lang = { git = "https:\/\/github.com\/clockwork-xyz\/anchor", branch = "'${new_anchor_version}'" }/g' "$toml" 37 | # sed -i '' -e 's/^anchor-spl =.*/anchor-spl = { git = "https:\/\/github.com\/clockwork-xyz\/anchor", branch = "'${new_anchor_version}'" }/g' "$toml" 38 | # sed -i '' -e 's/^clockwork-sdk =.*/clockwork-sdk = { git = "https:\/\/github.com\/clockwork-xyz\/clockwork", tag = "v'${new_version}'" }/g' "$toml" 39 | # done 40 | 41 | 42 | # Update Clients and Programs Cargo.tomls 43 | echo "Updating clockwork-sdk" 44 | declare TOMLS=() 45 | while IFS='' read -r line; do TOMLS+=("$line"); done < <(find . -mindepth 3 -type f -iname "Cargo.toml") 46 | for toml in "${TOMLS[@]}"; do 47 | sed -E -i '' -e "s:(solana-sdk = \")(=?)([0-9]+\.[0-9]+)\".*:\1\2${new_solana_version}\":" "$toml" 48 | sed -E -i '' -e "s:(solana-sdk = \{ version = \")(=?)[0-9]+\.[0-9]+\.[0-9]+(\".*):\1\2${new_solana_version}\3:" "$toml" 49 | sed -E -i '' -e "s:(solana-client = \")(=?)([0-9]+\.[0-9]+)\".*:\1\2${new_solana_version}\":" "$toml" 50 | sed -E -i '' -e "s:(solana-client = \{ version = \")(=?)[0-9]+\.[0-9]+\.[0-9]+(\".*):\1\2${new_solana_version}\3:" "$toml" 51 | sed -E -i '' -e "s:(clockwork-sdk = \")(=?)([0-9]+\.[0-9]+)\".*:\1\2${new_version}\":" "$toml" 52 | sed -E -i '' -e "s:(clockwork-sdk = \{ version = \")(=?)[0-9]+\.[0-9]+\.[0-9]+(\".*):\1\2${new_version}\3:" "$toml" 53 | done 54 | 55 | 56 | # Rebuild 57 | source scripts/build-all.sh 58 | 59 | # Update version 60 | echo $new_version > CLOCKWORK_SDK_VERSION 61 | #echo $new_anchor_version > ANCHOR_VERSION 62 | echo $new_solana_version > SOLANA_VERSION 63 | -------------------------------------------------------------------------------- /spl_transfer/README.md: -------------------------------------------------------------------------------- 1 | # **SPL Transfer** 2 | 3 | This example shows how to schedule a thread to transfer spl tokens every 10s using the typescript SDK only. 4 | 5 | --- 6 | 7 | Testing locally: 8 | ```bash 9 | cargo install -f --locked clockwork-cli 10 | clockwork localnet 11 | ``` 12 | 13 | Run the tests! 14 | ```bash 15 | yarn 16 | ``` 17 | 18 | ```bash 19 | yarn test 20 | ``` 21 | -------------------------------------------------------------------------------- /spl_transfer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spl-transfer", 3 | "version": "1.0.0", 4 | "description": "SPL Transfer Example", 5 | "scripts": { 6 | "test": "yarn run ts-mocha -p tsconfig.json -t 1000000 ./tests/main.ts" 7 | }, 8 | "dependencies": { 9 | "@clockwork-xyz/sdk": "^0.3.4", 10 | "@coral-xyz/anchor": "^0.27.0", 11 | "@solana/spl-token": "^0.3.6", 12 | "@solana/web3.js": "^1.73.0" 13 | }, 14 | "devDependencies": { 15 | "@types/chai": "^4.3.3", 16 | "@types/mocha": "^9.1.1", 17 | "chai": "^4.3.6", 18 | "mocha": "^10.0.0", 19 | "ts-mocha": "^10.0.0", 20 | "typescript": "^4.8.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spl_transfer/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from "@solana/web3.js"; 2 | import { ClockworkProvider } from "@clockwork-xyz/sdk"; 3 | 4 | const keypairFromFile = (path: string): Keypair => { 5 | return Keypair.fromSecretKey( 6 | Buffer.from(JSON.parse(require("fs").readFileSync(path, "utf-8"))) 7 | ); 8 | }; 9 | 10 | // helpers 11 | let lastThreadExec = BigInt(0); 12 | const waitForThreadExec = async ( 13 | clockworkProvider: ClockworkProvider, 14 | thread: PublicKey, 15 | maxWait: number = 60 16 | ) => { 17 | let i = 1; 18 | while (true) { 19 | const execContext = (await clockworkProvider.getThreadAccount(thread)) 20 | .execContext; 21 | if (execContext) { 22 | if ( 23 | lastThreadExec.toString() == "0" || 24 | execContext.lastExecAt > lastThreadExec 25 | ) { 26 | lastThreadExec = execContext.lastExecAt; 27 | break; 28 | } 29 | } 30 | if (i == maxWait) throw Error("Timeout"); 31 | i += 1; 32 | await new Promise((r) => setTimeout(r, i * 1000)); 33 | } 34 | }; 35 | 36 | export { keypairFromFile, waitForThreadExec }; 37 | -------------------------------------------------------------------------------- /spl_transfer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015"], 4 | "module": "commonjs", 5 | "target": "es6", 6 | "esModuleInterop": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /subscriptions/.env.example: -------------------------------------------------------------------------------- 1 | CLIENT_PRIVATE="[255,255,...255]" 2 | SUBSCRIPTION="qweqweqwe" 3 | SUBSCRIPTION_THREAD="qweqweqwe" 4 | SUBSCRIPTION_BANK="qweqweqwe" 5 | SUBSCRIBER="qweqweqwe" 6 | SUBSCRIBER_TOKEN_ACCOUNT="qweqweqwe" 7 | MINT="qweqweqwe" 8 | SUBSCRIPTION_ID="123" 9 | -------------------------------------------------------------------------------- /subscriptions/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .env 9 | -------------------------------------------------------------------------------- /subscriptions/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /subscriptions/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | [programs.devnet] 5 | subscriptions_program = "Dhznaw95UUbADF49e4AXqjFo56nTFttFUtWU3ptEN7Ch" 6 | 7 | [registry] 8 | url = "https://api.apr.dev" 9 | 10 | [provider] 11 | cluster = "devnet" 12 | wallet = "~/.config/solana/id.json" 13 | 14 | [scripts] 15 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 16 | -------------------------------------------------------------------------------- /subscriptions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "client", 4 | "programs/*", 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | 16 | [patch.crates-io] 17 | anchor-lang = { git = "https://github.com/clockwork-xyz/anchor", branch = "0.25.0-solana.1.13.4" } 18 | anchor-spl = { git = "https://github.com/clockwork-xyz/anchor", branch = "0.25.0-solana.1.13.4" } 19 | clockwork-sdk = { git = "https://github.com/clockwork-xyz/clockwork", tag = "v1.3.9" } 20 | 21 | -------------------------------------------------------------------------------- /subscriptions/README.md: -------------------------------------------------------------------------------- 1 | ### Prerequisite 2 | 3 | 1. Create `.env` file and initialize it with the following content 4 | 5 | ``` 6 | CLIENT_PRIVATE= 7 | SUBSCRIPTION= 8 | SUBSCRIPTION_THREAD= 9 | SUBSCRIPTION_BANK= 10 | SUBSCRIBER= 11 | SUBSCRIBER_TOKEN_ACCOUNT= 12 | MINT= 13 | SUBSCRIPTION_ID= 14 | ``` 15 | 16 | After Running each command, update your .env file accordingly. 17 | 18 | ### Usage 19 | 1. Initialize mint and token accounts for testing purposes. 20 | 21 | ``` 22 | cargo run -- --command create_mint 23 | ``` 24 | 25 | 2. Initialize a Subscription with the specified `recurrent_amount` 26 | 27 | ``` 28 | cargo run -- --command create_subscription --recurrent_amount 29 | ``` 30 | 31 | 3. Deactivate a Subscription. 32 | 33 | ``` 34 | cargo run -- --command deactivate_subscription 35 | ``` 36 | 37 | 4. Withdraw from the Subscription if you're the owner. 38 | 39 | ``` 40 | cargo run -- --command withdraw 41 | ``` 42 | 43 | 5. Create a Subscriber for a Subscription. 44 | 45 | ``` 46 | cargo run -- --command create_subscriber 47 | ``` 48 | 49 | 6. Subscribe to a Subscription. 50 | 51 | ``` 52 | cargo run -- --command subscribe 53 | ``` 54 | 55 | 7. Unsubscribe from a Subscription. 56 | 57 | ``` 58 | cargo run -- --command unsubscribe 59 | ``` 60 | -------------------------------------------------------------------------------- /subscriptions/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client-subscriptions-program" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | subscriptions-program = { path = "../programs/subscriptions", features = ["no-entrypoint"], version = "0.1.0" } 8 | clap = { version = "4.0.28", features = ["derive"] } 9 | clockwork-sdk = { version = "1.3.9", features = ["client"] } 10 | clockwork-crank = "1.0.5" 11 | anchor-spl = "0.25.0" 12 | anchor-lang = "0.25.0" 13 | solana-sdk = "1.10.34" 14 | solana-client = "1.10.34" 15 | dotenv = "0.15.0" 16 | serde_json = "1.0.89" 17 | serde = "1.0.147" 18 | rand = "0.8.5" 19 | bs58 = "0.4.0" 20 | 21 | [features] 22 | devnet = [] 23 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/create_mint.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | clockwork_sdk::client::{Client, ClientResult}, 4 | }; 5 | 6 | pub fn create_mint(client: &Client) -> ClientResult<()> { 7 | let mint = client 8 | .create_token_mint(&client.payer_pubkey(), 9) 9 | .unwrap() 10 | .pubkey(); 11 | 12 | let subscriber_token_account = client 13 | .create_associated_token_account(&client.payer, &client.payer_pubkey(), &mint) 14 | .unwrap(); 15 | 16 | client 17 | .mint_to(&client.payer, &mint, &subscriber_token_account, 100000, 9) 18 | .unwrap(); 19 | 20 | println!("- - - - - - - - - - UPDATE YOUR .ENV FILE - - - - - - - - - -"); 21 | println!("MINT=\"{:?}\"", mint); 22 | println!( 23 | "SUBSCRIBER_TOKEN_ACCOUNT=\"{:?}\"", 24 | subscriber_token_account 25 | ); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/create_subscriber.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | anchor_spl::token, 5 | clockwork_sdk::client::{Client, ClientResult}, 6 | clockwork_sdk::thread_program, 7 | solana_sdk::{ 8 | instruction::{AccountMeta, Instruction}, 9 | system_program, 10 | }, 11 | }; 12 | 13 | pub fn create_subscriber( 14 | client: &Client, 15 | subscriber: Pubkey, 16 | subscription: Pubkey, 17 | subscription_thread: Pubkey, 18 | subscriber_token_account: Pubkey, 19 | mint: Pubkey, 20 | subscription_bank: Pubkey, 21 | subscriber_bump: u8, 22 | ) -> ClientResult<()> { 23 | let create_subscriber_ix = Instruction { 24 | program_id: subscriptions_program::ID, 25 | accounts: vec![ 26 | AccountMeta::new(client.payer_pubkey(), true), 27 | AccountMeta::new(subscriber, false), 28 | AccountMeta::new(subscriber_token_account, false), 29 | AccountMeta::new_readonly(subscription, false), 30 | AccountMeta::new(subscription_bank, false), 31 | AccountMeta::new(subscription_thread, false), 32 | AccountMeta::new_readonly(mint, false), 33 | AccountMeta::new_readonly(thread_program::ID, false), 34 | AccountMeta::new_readonly(system_program::ID, false), 35 | AccountMeta::new_readonly(token::ID, false), 36 | ], 37 | data: subscriptions_program::instruction::CreateSubscriber { subscriber_bump }.data(), 38 | }; 39 | 40 | send_and_confirm_tx( 41 | client, 42 | [create_subscriber_ix].to_vec(), 43 | None, 44 | "create_subscriber".to_string(), 45 | )?; 46 | println!("- - - - - - - - - - UPDATE YOUR .ENV FILE - - - - - - - - - -"); 47 | println!("SUBSCRIPTION_THREAD=\"{:?}\"", subscription_thread); 48 | println!("SUBSCRIBER=\"{:?}\"", subscriber); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/create_subscription.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, solana_program::sysvar, InstructionData}, 4 | anchor_spl::token, 5 | clockwork_sdk::client::{Client, ClientResult}, 6 | solana_sdk::{ 7 | instruction::{AccountMeta, Instruction}, 8 | system_program, 9 | }, 10 | }; 11 | 12 | pub fn create_subscription( 13 | client: &Client, 14 | subscription_bank: Pubkey, 15 | mint: Pubkey, 16 | subscription: Pubkey, 17 | recurrent_amount: u64, 18 | schedule: String, 19 | is_active: bool, 20 | subscription_id: u64, 21 | bump: u8, 22 | ) -> ClientResult<()> { 23 | let create_subscription_ix = Instruction { 24 | program_id: subscriptions_program::ID, 25 | accounts: vec![ 26 | AccountMeta::new(client.payer_pubkey(), true), 27 | AccountMeta::new(subscription_bank, false), 28 | AccountMeta::new_readonly(mint, false), 29 | AccountMeta::new(subscription, false), 30 | AccountMeta::new_readonly(system_program::ID, false), 31 | AccountMeta::new_readonly(token::ID, false), 32 | AccountMeta::new_readonly(sysvar::rent::ID, false), 33 | ], 34 | data: subscriptions_program::instruction::CreateSubscription { 35 | recurrent_amount, 36 | schedule, 37 | mint, 38 | is_active, 39 | subscription_id, 40 | bump, 41 | } 42 | .data(), 43 | }; 44 | 45 | send_and_confirm_tx( 46 | client, 47 | [create_subscription_ix].to_vec(), 48 | None, 49 | "create_subscription".to_string(), 50 | )?; 51 | println!("- - - - - - - - - - UPDATE YOUR .ENV FILE - - - - - - - - - -"); 52 | println!("SUBSCRIPTION=\"{:?}\"", subscription); 53 | println!("SUBSCRIPTION_BANK=\"{:?}\"", subscription_bank); 54 | println!("SUBSCRIPTION_ID=\"{:?}\"", subscription_id); 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/deactivate_subscription.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | clockwork_sdk::client::{Client, ClientResult}, 5 | solana_sdk::instruction::{AccountMeta, Instruction}, 6 | }; 7 | 8 | pub fn deactivate_subscription( 9 | client: &Client, 10 | subscription: Pubkey, 11 | mint: Pubkey, 12 | ) -> ClientResult<()> { 13 | let deactivate_subscription_ix = Instruction { 14 | program_id: subscriptions_program::ID, 15 | accounts: vec![ 16 | AccountMeta::new(client.payer_pubkey(), true), 17 | AccountMeta::new(subscription, false), 18 | AccountMeta::new_readonly(mint, false), 19 | ], 20 | data: subscriptions_program::instruction::DeactivateSubscription {}.data(), 21 | }; 22 | 23 | send_and_confirm_tx( 24 | client, 25 | [deactivate_subscription_ix].to_vec(), 26 | None, 27 | "deactivate_subscription".to_string(), 28 | )?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_mint; 2 | pub mod create_subscriber; 3 | pub mod create_subscription; 4 | pub mod deactivate_subscription; 5 | pub mod subscribe; 6 | pub mod unsubscribe; 7 | pub mod update_authority; 8 | pub mod withdraw; 9 | 10 | pub use create_mint::*; 11 | pub use create_subscriber::*; 12 | pub use create_subscription::*; 13 | pub use deactivate_subscription::*; 14 | pub use subscribe::*; 15 | pub use unsubscribe::*; 16 | pub use update_authority::*; 17 | pub use withdraw::*; 18 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/subscribe.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | anchor_spl::token, 5 | clockwork_sdk::client::{Client, ClientResult}, 6 | clockwork_sdk::thread_program, 7 | solana_sdk::instruction::{AccountMeta, Instruction}, 8 | }; 9 | 10 | pub fn subscribe( 11 | client: &Client, 12 | subscriber: Pubkey, 13 | subscription: Pubkey, 14 | subscriber_token_account: Pubkey, 15 | subscription_bank: Pubkey, 16 | mint: Pubkey, 17 | subscription_thread: Pubkey, 18 | ) -> ClientResult<()> { 19 | let subscribe_ix = Instruction { 20 | program_id: subscriptions_program::ID, 21 | accounts: vec![ 22 | AccountMeta::new(client.payer_pubkey(), true), 23 | AccountMeta::new(subscriber, false), 24 | AccountMeta::new(subscriber_token_account, false), 25 | AccountMeta::new(subscription, false), 26 | AccountMeta::new(subscription_bank, false), 27 | AccountMeta::new(subscription_thread, false), 28 | AccountMeta::new_readonly(mint, false), 29 | AccountMeta::new_readonly(token::ID, false), 30 | AccountMeta::new_readonly(thread_program::ID, false), 31 | ], 32 | data: subscriptions_program::instruction::Subscribe {}.data(), 33 | }; 34 | 35 | send_and_confirm_tx( 36 | client, 37 | [subscribe_ix].to_vec(), 38 | None, 39 | "subscribe".to_string(), 40 | )?; 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/unsubscribe.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | clockwork_sdk::client::{Client, ClientResult}, 5 | solana_sdk::instruction::{AccountMeta, Instruction}, 6 | }; 7 | 8 | pub fn unsubscribe(client: &Client, subscriber: Pubkey, subscription: Pubkey) -> ClientResult<()> { 9 | let unsubscribe_ix = Instruction { 10 | program_id: subscriptions_program::ID, 11 | accounts: vec![ 12 | AccountMeta::new(client.payer_pubkey(), true), 13 | AccountMeta::new(subscriber, false), 14 | AccountMeta::new(subscription, false), 15 | ], 16 | data: subscriptions_program::instruction::Unsubscribe {}.data(), 17 | }; 18 | 19 | send_and_confirm_tx( 20 | client, 21 | [unsubscribe_ix].to_vec(), 22 | None, 23 | "unsubscribe".to_string(), 24 | )?; 25 | 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/update_authority.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | clockwork_sdk::client::{Client, ClientResult}, 5 | solana_sdk::instruction::{AccountMeta, Instruction}, 6 | }; 7 | 8 | pub fn update_auhority( 9 | client: &Client, 10 | subscription: Pubkey, 11 | new_authority: Pubkey, 12 | ) -> ClientResult<()> { 13 | let update_authority_ix = Instruction { 14 | program_id: subscriptions_program::ID, 15 | accounts: vec![ 16 | AccountMeta::new(client.payer_pubkey(), true), 17 | AccountMeta::new(subscription, false), 18 | ], 19 | data: subscriptions_program::instruction::UpdateAuthority { new_authority }.data(), 20 | }; 21 | 22 | send_and_confirm_tx( 23 | client, 24 | [update_authority_ix].to_vec(), 25 | None, 26 | "update_auhority".to_string(), 27 | )?; 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /subscriptions/client/src/commands/withdraw.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | anchor_lang::{prelude::Pubkey, InstructionData}, 4 | anchor_spl::token, 5 | clockwork_sdk::client::{Client, ClientResult}, 6 | solana_sdk::instruction::{AccountMeta, Instruction}, 7 | }; 8 | 9 | pub fn withdraw( 10 | client: &Client, 11 | payer_token_account: Pubkey, 12 | subscription_bank: Pubkey, 13 | subscription: Pubkey, 14 | mint: Pubkey, 15 | ) -> ClientResult<()> { 16 | let withdraw_ix = Instruction { 17 | program_id: subscriptions_program::ID, 18 | accounts: vec![ 19 | AccountMeta::new(client.payer_pubkey(), true), 20 | AccountMeta::new(payer_token_account, false), 21 | AccountMeta::new(subscription, false), 22 | AccountMeta::new(subscription_bank, false), 23 | AccountMeta::new_readonly(mint, false), 24 | AccountMeta::new_readonly(token::ID, false), 25 | ], 26 | data: subscriptions_program::instruction::Withdraw {}.data(), 27 | }; 28 | 29 | send_and_confirm_tx(client, [withdraw_ix].to_vec(), None, "withdraw".to_string())?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /subscriptions/deploy.sh: -------------------------------------------------------------------------------- 1 | ../scripts/deploy.sh -------------------------------------------------------------------------------- /subscriptions/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /subscriptions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.25.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subscriptions-program" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "subscriptions_program" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [profile.release] 19 | overflow-checks = true 20 | 21 | [dependencies] 22 | anchor-lang = "0.25.0" 23 | anchor-spl = { version = "0.25.0", features = ["token"] } 24 | clockwork-sdk = { version = "1.3.9", features = ["thread"] } 25 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/create_subscription.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::{prelude::*, solana_program::sysvar}, 4 | anchor_spl::token::{Mint, TokenAccount}, 5 | std::mem::size_of, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | #[instruction(recurrent_amount:u64,schedule:String,mint:Pubkey,is_active:bool,subscription_id: u64)] 10 | pub struct CreateSubscription<'info> { 11 | #[account(mut)] 12 | pub owner: Signer<'info>, 13 | #[account( 14 | init, 15 | payer = owner, 16 | token::mint = mint, 17 | token::authority = subscription, 18 | seeds=[ 19 | subscription.key().as_ref(), 20 | owner.key().as_ref(), 21 | "subscription_bank".as_bytes(), 22 | ], 23 | bump, 24 | )] 25 | pub subscription_bank: Account<'info, TokenAccount>, 26 | pub mint: Account<'info, Mint>, 27 | 28 | #[account( 29 | init, 30 | payer = owner, 31 | space = 8 + size_of::(), 32 | seeds=[ 33 | SEED_SUBSCRIPTION, 34 | owner.key().as_ref(), 35 | &subscription_id.to_be_bytes() 36 | ], 37 | bump, 38 | )] 39 | pub subscription: Account<'info, Subscription>, 40 | 41 | pub system_program: Program<'info, System>, 42 | #[account(address = anchor_spl::token::ID)] 43 | pub token_program: Program<'info, anchor_spl::token::Token>, 44 | #[account(address = sysvar::rent::ID)] 45 | pub rent: Sysvar<'info, Rent>, 46 | } 47 | 48 | impl<'info> CreateSubscription<'_> { 49 | pub fn process( 50 | &mut self, 51 | recurrent_amount: u64, 52 | schedule: String, 53 | mint: Pubkey, 54 | is_active: bool, 55 | subscription_id: u64, 56 | bump: u8, 57 | ) -> Result<()> { 58 | let Self { 59 | owner, 60 | subscription, 61 | .. 62 | } = self; 63 | 64 | subscription.new( 65 | owner.key(), 66 | mint, 67 | recurrent_amount, 68 | schedule, 69 | is_active, 70 | subscription_id, 71 | bump, 72 | )?; 73 | 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/deactivate_subscription.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*, anchor_spl::token::Mint}; 2 | 3 | #[derive(Accounts)] 4 | pub struct DeactivateSubscription<'info> { 5 | #[account(mut, address=subscription.owner)] 6 | pub owner: Signer<'info>, 7 | #[account(mut, address = Subscription::pda(subscription.owner.key(),subscription.subscription_id.clone()).0, has_one=owner)] 8 | pub subscription: Account<'info, Subscription>, 9 | #[account(address=subscription.mint)] 10 | pub mint: Account<'info, Mint>, 11 | } 12 | 13 | impl<'info> DeactivateSubscription<'_> { 14 | pub fn process(&mut self) -> Result<()> { 15 | let Self { subscription, .. } = self; 16 | 17 | subscription.is_active = false; 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_subscriber; 2 | pub mod create_subscription; 3 | pub mod deactivate_subscription; 4 | pub mod disburse_payment; 5 | pub mod subscribe; 6 | pub mod unsubscribe; 7 | pub mod update_authority; 8 | pub mod withdraw; 9 | 10 | pub use create_subscriber::*; 11 | pub use create_subscription::*; 12 | pub use deactivate_subscription::*; 13 | pub use disburse_payment::*; 14 | pub use subscribe::*; 15 | pub use unsubscribe::*; 16 | pub use update_authority::*; 17 | pub use withdraw::*; 18 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/subscribe.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{error::ErrorCode, state::*}, 3 | anchor_lang::prelude::*, 4 | anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer}, 5 | clockwork_sdk::thread_program::{self, accounts::Thread, ThreadProgram}, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | pub struct Subscribe<'info> { 10 | #[account(mut)] 11 | pub payer: Signer<'info>, 12 | #[account( 13 | mut, 14 | address = Subscriber::pda(payer.key(),subscription.key()).0, 15 | )] 16 | pub subscriber: Account<'info, Subscriber>, 17 | #[account( 18 | mut, 19 | associated_token::mint = mint, 20 | associated_token::authority = payer 21 | )] 22 | pub subscriber_token_account: Account<'info, TokenAccount>, 23 | 24 | #[account(mut, address = Subscription::pda(subscription.owner.key(),subscription.subscription_id.clone()).0)] 25 | pub subscription: Account<'info, Subscription>, 26 | #[account( 27 | mut, 28 | token::mint = mint, 29 | token::authority = subscription, 30 | address = Subscription::bank_pda(subscription.key(),subscription.owner.key()).0 31 | )] 32 | pub subscription_bank: Account<'info, TokenAccount>, 33 | #[account(address = Thread::pubkey(subscriber.key(),"subscriber_thread".to_string()))] 34 | pub subscription_thread: Box>, 35 | 36 | #[account(address=subscription.mint)] 37 | pub mint: Account<'info, Mint>, 38 | 39 | pub token_program: Program<'info, Token>, 40 | #[account(address = thread_program::ID)] 41 | pub thread_program: Program<'info, ThreadProgram>, 42 | } 43 | 44 | impl<'info> Subscribe<'_> { 45 | pub fn process(&mut self) -> Result<()> { 46 | let Self { 47 | payer, 48 | subscriber, 49 | subscription, 50 | subscriber_token_account, 51 | token_program, 52 | subscription_bank, 53 | thread_program, 54 | subscription_thread, 55 | .. 56 | } = self; 57 | 58 | require!( 59 | subscriber_token_account.amount >= subscription.recurrent_amount, 60 | ErrorCode::InsuffiscientAmount 61 | ); 62 | require!(subscription.is_active, ErrorCode::SubscriptionInactive); 63 | 64 | token::transfer( 65 | CpiContext::new( 66 | token_program.to_account_info(), 67 | Transfer { 68 | authority: payer.to_account_info(), 69 | from: subscriber_token_account.to_account_info(), 70 | to: subscription_bank.to_account_info(), 71 | }, 72 | ), 73 | subscription.recurrent_amount, 74 | )?; 75 | 76 | clockwork_sdk::thread_program::cpi::thread_resume(CpiContext::new_with_signer( 77 | thread_program.to_account_info(), 78 | clockwork_sdk::thread_program::cpi::accounts::ThreadResume { 79 | authority: subscriber.to_account_info(), 80 | thread: subscription_thread.to_account_info(), 81 | }, 82 | &[&[ 83 | SEED_SUBSCRIBER, 84 | payer.key().as_ref(), 85 | subscription.key().as_ref(), 86 | &[subscriber.bump], 87 | ]], 88 | ))?; 89 | 90 | subscriber.is_active = true; 91 | subscriber.last_transfer_at = Some(Clock::get().unwrap().unix_timestamp); 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/unsubscribe.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct Unsubscribe<'info> { 5 | #[account(mut)] 6 | pub payer: Signer<'info>, 7 | #[account( 8 | mut, 9 | address = Subscriber::pda(payer.key(),subscription.key()).0, 10 | )] 11 | pub subscriber: Account<'info, Subscriber>, 12 | 13 | #[account(mut, address = Subscription::pda(subscription.owner.key(),subscription.subscription_id.clone()).0)] 14 | pub subscription: Account<'info, Subscription>, 15 | } 16 | 17 | impl<'info> Unsubscribe<'_> { 18 | pub fn process(&mut self) -> Result<()> { 19 | let Self { subscriber, .. } = self; 20 | 21 | subscriber.is_active = false; 22 | 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/update_authority.rs: -------------------------------------------------------------------------------- 1 | use {crate::state::*, anchor_lang::prelude::*}; 2 | 3 | #[derive(Accounts)] 4 | pub struct UpdateAuthority<'info> { 5 | #[account(mut)] 6 | pub owner: Signer<'info>, 7 | #[account(mut, address = Subscription::pda(subscription.owner.key(),subscription.subscription_id.clone()).0, has_one=owner)] 8 | pub subscription: Account<'info, Subscription>, 9 | } 10 | 11 | impl<'info> UpdateAuthority<'_> { 12 | pub fn process(&mut self, new_authority: Pubkey) -> Result<()> { 13 | let Self { subscription, .. } = self; 14 | 15 | subscription.owner = new_authority; 16 | 17 | Ok(()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/contexts/withdraw.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::*, 3 | anchor_lang::prelude::*, 4 | anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer}, 5 | }; 6 | 7 | #[derive(Accounts)] 8 | pub struct Withdraw<'info> { 9 | #[account(mut)] 10 | pub owner: Signer<'info>, 11 | #[account( 12 | mut, 13 | token::mint = mint, 14 | token::authority = owner, 15 | )] 16 | pub payer_token_account: Account<'info, TokenAccount>, 17 | 18 | #[account(mut, address = Subscription::pda(subscription.owner.key(),subscription.subscription_id.clone()).0, has_one=owner)] 19 | pub subscription: Account<'info, Subscription>, 20 | #[account( 21 | mut, 22 | token::mint = mint, 23 | token::authority = subscription, 24 | address = Subscription::bank_pda(subscription.key(),subscription.owner.key()).0 25 | )] 26 | pub subscription_bank: Account<'info, TokenAccount>, 27 | 28 | #[account(address=subscription.mint)] 29 | pub mint: Account<'info, Mint>, 30 | pub token_program: Program<'info, Token>, 31 | } 32 | 33 | impl<'info> Withdraw<'_> { 34 | pub fn process(&mut self) -> Result<()> { 35 | let Self { 36 | owner, 37 | subscription, 38 | token_program, 39 | payer_token_account, 40 | subscription_bank, 41 | .. 42 | } = self; 43 | 44 | token::transfer( 45 | CpiContext::new_with_signer( 46 | token_program.to_account_info(), 47 | Transfer { 48 | authority: subscription.to_account_info(), 49 | from: subscription_bank.to_account_info(), 50 | to: payer_token_account.to_account_info(), 51 | }, 52 | &[&[ 53 | SEED_SUBSCRIPTION, 54 | owner.key().as_ref(), 55 | &subscription.subscription_id.to_be_bytes(), 56 | &[subscription.bump], 57 | ]], 58 | ), 59 | subscription_bank.amount, 60 | )?; 61 | 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum ErrorCode { 5 | #[msg("Insuffiscient amount to transfer")] 6 | InsuffiscientAmount, 7 | #[msg("Subscription is inactive")] 8 | SubscriptionInactive, 9 | #[msg("payer is not the owner of the subscription")] 10 | NotOwner, 11 | } 12 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("Dhznaw95UUbADF49e4AXqjFo56nTFttFUtWU3ptEN7Ch"); 4 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contexts; 2 | pub mod error; 3 | pub mod id; 4 | pub mod state; 5 | 6 | use anchor_lang::prelude::*; 7 | pub use contexts::*; 8 | pub use id::ID; 9 | 10 | #[program] 11 | pub mod subscriptions_program { 12 | 13 | use super::*; 14 | 15 | /* 16 | * Initialize subscription account 17 | */ 18 | pub fn create_subscription<'info>( 19 | ctx: Context, 20 | recurrent_amount: u64, 21 | schedule: String, 22 | mint: Pubkey, 23 | is_active: bool, 24 | subscription_id: u64, 25 | bump: u8, 26 | ) -> Result<()> { 27 | ctx.accounts.process( 28 | recurrent_amount, 29 | schedule, 30 | mint, 31 | is_active, 32 | subscription_id, 33 | bump, 34 | ) 35 | } 36 | 37 | /* 38 | * Deactivate subscription 39 | */ 40 | pub fn deactivate_subscription<'info>(ctx: Context) -> Result<()> { 41 | ctx.accounts.process() 42 | } 43 | 44 | /* 45 | * Withdraw from subscription bank 46 | */ 47 | pub fn withdraw<'info>(ctx: Context) -> Result<()> { 48 | ctx.accounts.process() 49 | } 50 | 51 | /* 52 | * Update Authority 53 | */ 54 | pub fn update_authority<'info>( 55 | ctx: Context, 56 | new_authority: Pubkey, 57 | ) -> Result<()> { 58 | ctx.accounts.process(new_authority) 59 | } 60 | 61 | /* 62 | * create subscriber 63 | */ 64 | pub fn create_subscriber<'info>( 65 | ctx: Context, 66 | subscriber_bump: u8, 67 | ) -> Result<()> { 68 | ctx.accounts.process(subscriber_bump) 69 | } 70 | 71 | /* 72 | * subscribe from a subscription 73 | */ 74 | pub fn subscribe<'info>(ctx: Context) -> Result<()> { 75 | ctx.accounts.process() 76 | } 77 | 78 | /* 79 | * unsubscribe from a subscription 80 | */ 81 | pub fn unsubscribe<'info>(ctx: Context) -> Result<()> { 82 | ctx.accounts.process() 83 | } 84 | 85 | /* 86 | * disburse payment from program authority's ATA to recipient's ATA 87 | */ 88 | pub fn disburse_payment<'info>( 89 | ctx: Context, 90 | ) -> Result { 91 | ctx.accounts.process() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod subscriber; 2 | pub mod subscription; 3 | 4 | pub use subscriber::*; 5 | pub use subscription::*; 6 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/state/subscriber.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_SUBSCRIBER: &[u8] = b"subscriber"; 7 | 8 | /** 9 | * Subscriber 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Subscriber { 15 | pub owner: Pubkey, 16 | pub subscription: Pubkey, 17 | pub is_active: bool, 18 | pub last_transfer_at: Option, 19 | pub bump: u8, 20 | } 21 | 22 | impl Subscriber { 23 | pub fn pda(owner: Pubkey, subscription: Pubkey) -> (Pubkey, u8) { 24 | Pubkey::find_program_address( 25 | &[SEED_SUBSCRIBER, owner.as_ref(), subscription.as_ref()], 26 | &crate::ID, 27 | ) 28 | } 29 | } 30 | 31 | impl TryFrom> for Subscriber { 32 | type Error = Error; 33 | fn try_from(data: Vec) -> std::result::Result { 34 | Subscriber::try_deserialize(&mut data.as_slice()) 35 | } 36 | } 37 | 38 | pub trait SubscriberAccount { 39 | fn new(&mut self, owner: Pubkey, subscription: Pubkey, is_active: bool, bump: u8) 40 | -> Result<()>; 41 | } 42 | 43 | impl SubscriberAccount for Account<'_, Subscriber> { 44 | fn new( 45 | &mut self, 46 | owner: Pubkey, 47 | subscription: Pubkey, 48 | is_active: bool, 49 | bump: u8, 50 | ) -> Result<()> { 51 | self.owner = owner; 52 | self.subscription = subscription; 53 | self.is_active = is_active; 54 | self.last_transfer_at = None; 55 | self.bump = bump; 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /subscriptions/programs/subscriptions/src/state/subscription.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{prelude::*, AnchorDeserialize}, 3 | std::convert::TryFrom, 4 | }; 5 | 6 | pub const SEED_SUBSCRIPTION: &[u8] = b"subscription"; 7 | 8 | /** 9 | * Subscription 10 | */ 11 | 12 | #[account] 13 | #[derive(Debug)] 14 | pub struct Subscription { 15 | pub owner: Pubkey, 16 | pub mint: Pubkey, 17 | pub recurrent_amount: u64, 18 | pub schedule: String, 19 | pub is_active: bool, 20 | pub subscription_id: u64, 21 | pub bump: u8, 22 | } 23 | 24 | impl Subscription { 25 | pub fn pda(owner: Pubkey, subscription_id: u64) -> (Pubkey, u8) { 26 | Pubkey::find_program_address( 27 | &[ 28 | SEED_SUBSCRIPTION, 29 | owner.as_ref(), 30 | &subscription_id.to_be_bytes(), 31 | ], 32 | &crate::ID, 33 | ) 34 | } 35 | 36 | pub fn bank_pda(subscription: Pubkey, owner: Pubkey) -> (Pubkey, u8) { 37 | Pubkey::find_program_address( 38 | &[ 39 | subscription.as_ref(), 40 | owner.as_ref(), 41 | "subscription_bank".as_bytes(), 42 | ], 43 | &crate::ID, 44 | ) 45 | } 46 | } 47 | 48 | impl TryFrom> for Subscription { 49 | type Error = Error; 50 | fn try_from(data: Vec) -> std::result::Result { 51 | Subscription::try_deserialize(&mut data.as_slice()) 52 | } 53 | } 54 | 55 | pub trait SubscriptionAccount { 56 | fn new( 57 | &mut self, 58 | owner: Pubkey, 59 | mint: Pubkey, 60 | recurrent_amount: u64, 61 | schedule: String, 62 | is_active: bool, 63 | subscription_id: u64, 64 | bump: u8, 65 | ) -> Result<()>; 66 | } 67 | 68 | impl SubscriptionAccount for Account<'_, Subscription> { 69 | fn new( 70 | &mut self, 71 | owner: Pubkey, 72 | mint: Pubkey, 73 | recurrent_amount: u64, 74 | schedule: String, 75 | is_active: bool, 76 | subscription_id: u64, 77 | bump: u8, 78 | ) -> Result<()> { 79 | self.owner = owner; 80 | self.mint = mint; 81 | self.recurrent_amount = recurrent_amount; 82 | self.schedule = schedule; 83 | self.is_active = is_active; 84 | self.subscription_id = subscription_id; 85 | self.bump = bump; 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /subscriptions/tests/subscriptions.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { Program } from "@project-serum/anchor"; 3 | import { Subscriptions } from "../target/types/subscriptions"; 4 | 5 | describe("subscriptions", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.Subscriptions as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /subscriptions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /utils/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | declare const print_address: (label: any, address: any) => void; 3 | declare const print_thread: (clockworkProvider: any, address: any) => Promise; 4 | declare const print_tx: (label: any, address: any) => void; 5 | declare const stream_program_logs: (programId: any) => void; 6 | declare const verifyAmount: (connection: any, ata: any, expectedAmount: any) => Promise; 7 | declare const waitForThreadExec: (clockworkProvider: any, thread: PublicKey, maxWait?: number) => Promise; 8 | export { print_address, print_thread, print_tx, stream_program_logs, verifyAmount, waitForThreadExec, }; 9 | -------------------------------------------------------------------------------- /utils/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { spawn } from "child_process"; 3 | import { PublicKey } from "@solana/web3.js"; 4 | import { getAccount } from "@solana/spl-token"; 5 | // import { ClockworkProvider, PAYER_PUBKEY } from "@clockwork-xyz/sdk"; 6 | 7 | const print_address = (label, address) => { 8 | console.log(`${label}: https://explorer.solana.com/address/${address}?cluster=devnet`); 9 | } 10 | 11 | const print_thread = async (clockworkProvider, address) => { 12 | const threadAccount = await clockworkProvider.getThreadAccount(address); 13 | console.log("\nThread: ", threadAccount, "\n"); 14 | print_address("🧵 Thread", address); 15 | console.log("\n") 16 | } 17 | 18 | const print_tx = (label, address) => { 19 | console.log(`${label}: https://explorer.solana.com/tx/${address}?cluster=devnet`); 20 | } 21 | 22 | const stream_program_logs = (programId) => { 23 | const cmd = spawn("solana", ["logs", "-u", "devnet", programId.toString()]); 24 | cmd.stdout.on("data", data => { 25 | console.log(`Program Logs: ${data}`); 26 | }); 27 | } 28 | 29 | const verifyAmount = async (connection, ata, expectedAmount) => { 30 | const amount = (await getAccount(connection, ata)).amount; 31 | assert.equal(amount.toString(), expectedAmount.toString()); 32 | return amount; 33 | } 34 | 35 | let lastThreadExec = BigInt(0); 36 | const waitForThreadExec = async (clockworkProvider, thread: PublicKey, maxWait: number = 60) => { 37 | let i = 1; 38 | while (true) { 39 | const execContext = (await clockworkProvider.getThreadAccount(thread)).execContext; 40 | if (execContext) { 41 | if (lastThreadExec.toString() == "0" || execContext.lastExecAt > lastThreadExec) { 42 | lastThreadExec = execContext.lastExecAt; 43 | break; 44 | } 45 | } 46 | if (i == maxWait) throw Error("Timeout"); 47 | i += 1; 48 | await new Promise((r) => setTimeout(r, i * 1000)); 49 | } 50 | } 51 | 52 | export { 53 | print_address, 54 | print_thread, 55 | print_tx, 56 | stream_program_logs, 57 | verifyAmount, 58 | waitForThreadExec, 59 | } 60 | -------------------------------------------------------------------------------- /utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utils", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "typescript": "^5.0.4" 15 | }, 16 | "dependencies": { 17 | "@solana/spl-token": "^0.3.7", 18 | "@types/chai": "^4.3.4", 19 | "@types/mocha": "^10.0.1", 20 | "chai": "^4.3.7", 21 | "mocha": "^10.2.0", 22 | "ts-node": "^10.9.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "noImplicitAny": false 9 | } 10 | } 11 | --------------------------------------------------------------------------------