├── .gitignore ├── programs └── gig-basic-contract │ ├── Xargo.toml │ ├── src │ ├── state │ │ ├── mod.rs │ │ ├── contract.rs │ │ ├── job_contract.rs │ │ └── hourly_contract.rs │ ├── constants.rs │ ├── instructions │ │ ├── mod.rs │ │ ├── pause_hourly_contract.rs │ │ ├── resume_hourly_contract.rs │ │ ├── end_hourly_contract.rs │ │ ├── update_worked_hour.rs │ │ ├── admin_withdraw_job.rs │ │ ├── buyer_approve.rs │ │ ├── accept_contract_on_buyer.rs │ │ ├── pay_worked_hour.rs │ │ ├── activate_contract.rs │ │ ├── activate_hourly_contract.rs │ │ ├── start_contract_on_seller.rs │ │ ├── job_listing_with_one_fee_employer(usdc fee).rs │ │ ├── start_contract_on_buyer.rs │ │ ├── job_listing_with_one_fee_employer.rs │ │ ├── start_hourly_contract_on_buyer.rs │ │ ├── admin_withdraw_funds.rs │ │ ├── job_listing_with_feature_employer.rs │ │ ├── seller_approve_hourly_contract.rs │ │ ├── seller_approve.rs │ │ └── admin_approve.rs │ ├── errors.rs │ └── lib.rs │ └── Cargo.toml ├── tsconfig.json ├── Cargo.toml ├── migrations └── deploy.ts ├── Anchor.toml ├── package.json └── tests ├── job-listing-test-js.test ├── job-listing-test.test ├── gig-basic-contract2.test ├── gig-basic-contract.test.test ├── admin-withdraw.ts └── gig-basic-contract.test /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | yarn.lock 3 | /.anchor 4 | Cargo.lock 5 | /node_modules -------------------------------------------------------------------------------- /programs/gig-basic-contract/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub use contract::*; 2 | pub use hourly_contract::*; 3 | pub use job_contract::*; 4 | 5 | pub mod contract; 6 | pub mod hourly_contract; 7 | pub mod job_contract; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = false 5 | skip-lint = false 6 | 7 | [programs.devnet] 8 | gig_basic_contract = "BccHg9A6gnaC9a9NWinzqE6r7491ohyFaHWjFeYFxsBV" 9 | 10 | [programs.localnet] 11 | gig_basic_contract = "BccHg9A6gnaC9a9NWinzqE6r7491ohyFaHWjFeYFxsBV" 12 | 13 | [programs.mainnet] 14 | gig_basic_contract = "BccHg9A6gnaC9a9NWinzqE6r7491ohyFaHWjFeYFxsBV" 15 | 16 | [registry] 17 | url = "https://api.apr.dev" 18 | 19 | [provider] 20 | cluster = "Mainnet" 21 | wallet = "/home/god/.config/solana/id.json" 22 | 23 | [scripts] 24 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 25 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gig-basic-contract" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "gig_basic_contract" 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.29.0", features = ["init-if-needed"] } 20 | anchor-spl = "0.29.0" 21 | solana-program = "1.18.3" 22 | spl-token = "4.0.0" 23 | bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.29.0", 8 | "@solana/spl-token": "^0.4.9", 9 | "@solana/web3.js": "^1.95.5", 10 | "uuid": "^10.0.0", 11 | "yarn": "^1.22.22" 12 | }, 13 | "devDependencies": { 14 | "@types/bn.js": "^5.1.0", 15 | "@types/chai": "^4.3.0", 16 | "@types/mocha": "^9.0.0", 17 | "chai": "^4.5.0", 18 | "mocha": "^9.0.3", 19 | "prettier": "^2.6.2", 20 | "ts-mocha": "^10.0.0", 21 | "typescript": "^4.3.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/constants.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::Pubkey; 2 | 3 | pub const CONTRACT_SEED: &str = "gig_contract"; 4 | pub const CONTRACT_NATIVE_SEED: &str = "gig_contract_native"; 5 | 6 | pub const DECIMAL: u32 = 6; // 8 for BPT, 6 for USDC 7 | 8 | pub const ADMIN_ADDRESS: Pubkey = anchor_lang::solana_program::pubkey!("384WTsaMenUoXE89jjJvLSWcG7hUeqN8pcxwV1KNVuHd"); 9 | pub const PAY_TOKEN_MINT_ADDRESS: Pubkey = anchor_lang::solana_program::pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC address for mainnet 10 | // pub const PAY_TOKEN_MINT_ADDRESS: Pubkey = anchor_lang::solana_program::pubkey!("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); // USDC address for devnet 11 | // pub const PAY_TOKEN_MINT_ADDRESS: Pubkey = anchor_lang::solana_program::pubkey!("HSvEJfU8hXUWFRodbVbRfwYb2p4DwSwpiMaoB7UDRVD4"); // USDT address for devnet 12 | // pub const PAY_TOKEN_MINT_ADDRESS: Pubkey = anchor_lang::solana_program::pubkey!("7FctSfSZ9GonfMrybp45hzoQyU71CEjjZFxxoSzqKWT"); // BPT address for devnet 13 | pub const SOL_KEY: Pubkey = anchor_lang::solana_program::pubkey!("So11111111111111111111111111111111111111112"); 14 | pub const EMPLOYER_REFERRAL: Pubkey = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub use start_contract_on_seller::*; 2 | pub use start_contract_on_buyer::*; 3 | pub use activate_contract::*; 4 | pub use accept_contract_on_buyer::*; 5 | pub use buyer_approve::*; 6 | pub use seller_approve::*; 7 | pub use admin_approve::*; 8 | pub use start_hourly_contract_on_buyer::*; 9 | pub use activate_hourly_contract::*; 10 | pub use update_worked_hour::*; 11 | pub use pay_worked_hour::*; 12 | pub use seller_approve_hourly_contract::*; 13 | pub use pause_hourly_contract::*; 14 | pub use resume_hourly_contract::*; 15 | pub use end_hourly_contract::*; 16 | pub use job_listing_with_one_fee_employer::*; 17 | pub use job_listing_with_feature_employer::*; 18 | pub use admin_withdraw_job::*; 19 | pub mod start_contract_on_seller; 20 | pub mod start_contract_on_buyer; 21 | pub mod activate_contract; 22 | pub mod accept_contract_on_buyer; 23 | pub mod buyer_approve; 24 | pub mod seller_approve; 25 | pub mod admin_approve; 26 | pub mod start_hourly_contract_on_buyer; 27 | pub mod activate_hourly_contract; 28 | pub mod update_worked_hour; 29 | pub mod pay_worked_hour; 30 | pub mod seller_approve_hourly_contract; 31 | pub mod pause_hourly_contract; 32 | pub mod resume_hourly_contract; 33 | pub mod end_hourly_contract; 34 | pub mod job_listing_with_one_fee_employer; 35 | pub mod job_listing_with_feature_employer; 36 | pub mod admin_withdraw_job; -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/errors.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum GigContractError { 5 | #[msg("Invalid seller is trying to release funds!")] 6 | InvalidSeller, 7 | #[msg("Invalid seller is trying to activate contract!")] 8 | InvalidActivator, 9 | #[msg("Invalid buyer is trying to accept contract!")] 10 | InvalidAcceptor, 11 | #[msg("Invalid buyer is trying to release funds!")] 12 | InvalidBuyer, 13 | #[msg("Invalid admin is trying to release funds!")] 14 | InvalidAdmin, 15 | #[msg("Dispute Amount should be 50 cent!")] 16 | InvalidDisputeAmount, 17 | #[msg("Contract is not active yet or already completed!")] 18 | CantRelease, 19 | #[msg("Contract status should be Created to accept!")] 20 | CantAccept, 21 | #[msg("Can not activate contract!")] 22 | CantActivate, 23 | #[msg("Contract is not pending or disputed yet so admin can't approve now or already completed!")] 24 | NotReadyYet, 25 | #[msg("Invalid payment token!")] 26 | PayTokenMintError, 27 | #[msg("Invalid pay amount!")] 28 | PayAmountError, 29 | #[msg("Hourly Contract was paused!")] 30 | HourlyContractPaused, 31 | #[msg("Hourly Contract was ended!")] 32 | HourlyContractEnded, 33 | #[msg("Exceeded weekly hours limit!")] 34 | WeeklyHoursLimitError, 35 | #[msg("It needs to be ready to pay!")] 36 | HourlyGigPayError, 37 | #[msg("Hourly Contract was not paid yet!")] 38 | HourlyContractNotPaidYet, 39 | #[msg("Invalid featured day!")] 40 | InvalidFeaturedDay, 41 | } -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/pause_hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn pause_hourly_contract( 19 | ctx: Context, 20 | contract_id: String, 21 | ) -> Result<()> { 22 | msg!("Pausing hourly contract on buyer side!"); 23 | let contract = &mut ctx.accounts.contract; 24 | 25 | // Check if the signer is a correct buyer 26 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidActivator); 27 | 28 | // Check if the contract is not ended. 29 | require!(contract.status != HourlyContractStatus::Ended, GigContractError::HourlyContractEnded); 30 | 31 | contract.paused = true; 32 | 33 | msg!("Paused hourly contract successfully!"); 34 | Ok(()) 35 | } 36 | 37 | #[derive(Accounts)] 38 | #[instruction(contract_id: String)] 39 | pub struct PauseHourlyContractContext<'info> { 40 | #[account(mut)] 41 | pub buyer: Signer<'info>, 42 | 43 | #[account( 44 | mut, 45 | seeds = [ 46 | CONTRACT_SEED.as_bytes(), 47 | &contract_id.as_bytes() 48 | ], 49 | bump, 50 | )] 51 | pub contract: Account<'info, HourlyContract>, 52 | 53 | pub system_program: Program<'info, System>, 54 | } 55 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/resume_hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn resume_hourly_contract( 19 | ctx: Context, 20 | contract_id: String, 21 | ) -> Result<()> { 22 | msg!("Resuming hourly contract on buyer side!"); 23 | let contract = &mut ctx.accounts.contract; 24 | 25 | // Check if the signer is a correct buyer 26 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidActivator); 27 | 28 | // Check if the contract is not ended. 29 | require!(contract.status != HourlyContractStatus::Ended, GigContractError::HourlyContractEnded); 30 | 31 | contract.paused = false; 32 | 33 | msg!("Resumed hourly contract successfully!"); 34 | Ok(()) 35 | } 36 | 37 | #[derive(Accounts)] 38 | #[instruction(contract_id: String)] 39 | pub struct ResumeHourlyContractContext<'info> { 40 | #[account(mut)] 41 | pub buyer: Signer<'info>, 42 | 43 | #[account( 44 | mut, 45 | seeds = [ 46 | CONTRACT_SEED.as_bytes(), 47 | &contract_id.as_bytes() 48 | ], 49 | bump, 50 | )] 51 | pub contract: Account<'info, HourlyContract>, 52 | 53 | pub system_program: Program<'info, System>, 54 | } 55 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/end_hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn end_hourly_contract( 19 | ctx: Context, 20 | contract_id: String, 21 | ) -> Result<()> { 22 | msg!("Ending hourly contract on buyer side!"); 23 | let contract = &mut ctx.accounts.contract; 24 | 25 | // Check if the signer is a correct buyer 26 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidActivator); 27 | 28 | // Check if the contract is not ended. 29 | require!(contract.status != HourlyContractStatus::Ended, GigContractError::HourlyContractEnded); 30 | 31 | contract.status = HourlyContractStatus::Ended; 32 | 33 | msg!("Ended hourly contract successfully!"); 34 | Ok(()) 35 | } 36 | 37 | #[derive(Accounts)] 38 | #[instruction(contract_id: String)] 39 | pub struct EndHourlyContractContext<'info> { 40 | #[account(mut)] 41 | pub buyer: Signer<'info>, 42 | 43 | #[account( 44 | mut, 45 | seeds = [ 46 | CONTRACT_SEED.as_bytes(), 47 | &contract_id.as_bytes() 48 | ], 49 | bump, 50 | )] 51 | pub contract: Account<'info, HourlyContract>, 52 | 53 | pub system_program: Program<'info, System>, 54 | } 55 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/state/contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use std::mem::size_of; 3 | 4 | use crate::constants::*; 5 | use crate::errors::{ 6 | GigContractError 7 | }; 8 | 9 | #[account] 10 | pub struct Contract { 11 | pub buyer: Pubkey, 12 | pub seller: Pubkey, 13 | pub buyer_referral: Pubkey, 14 | pub seller_referral: Pubkey, 15 | pub contract_id: String, 16 | pub start_time: u32, 17 | pub deadline: u32, 18 | pub amount: u64, 19 | pub dispute: u64, 20 | pub split: bool, 21 | pub seller_satisfied: bool, // regarding split decision 22 | pub buyer_approved: bool, 23 | pub seller_approved: bool, 24 | pub admin_approved: bool, 25 | pub status: ContractStatus, 26 | } 27 | 28 | impl Contract { 29 | pub const LEN: usize = size_of::(); 30 | } 31 | 32 | impl Default for Contract { 33 | #[inline] 34 | fn default() -> Contract { 35 | Contract { 36 | contract_id: "".to_string(), 37 | buyer: Pubkey::default(), 38 | seller: Pubkey::default(), 39 | buyer_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 40 | seller_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 41 | start_time: 0, 42 | deadline: 0, 43 | amount: 0, 44 | dispute: 0, 45 | split: false, 46 | seller_satisfied: false, 47 | buyer_approved: false, 48 | seller_approved: false, 49 | admin_approved: false, 50 | status: ContractStatus::NoExist, 51 | } 52 | } 53 | } 54 | 55 | #[derive(Eq, AnchorSerialize, AnchorDeserialize, Clone, PartialEq)] 56 | pub enum ContractStatus { 57 | NoExist, 58 | Created, 59 | Active, 60 | Accepted, 61 | Pending, 62 | Dispute, 63 | Completed, 64 | } 65 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/state/job_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use std::mem::size_of; 3 | 4 | use crate::constants::*; 5 | use crate::errors::{ 6 | GigContractError 7 | }; 8 | 9 | #[account] 10 | pub struct JobContract { 11 | pub employer: Pubkey, 12 | pub employee: Pubkey, 13 | pub employer_referral: Pubkey, 14 | pub employee_referral: Pubkey, 15 | pub contract_id: String, 16 | pub start_time: u32, 17 | pub deadline: u32, 18 | pub amount: u64, 19 | pub dispute: u64, 20 | pub split: bool, 21 | pub employer_satisfied: bool, // regarding split decision 22 | pub employer_approved: bool, 23 | pub employee_approved: bool, 24 | pub admin_approved: bool, 25 | pub status: JobContractStatus, 26 | pub featured: bool, 27 | pub featured_day: u8, 28 | } 29 | 30 | impl JobContract { 31 | pub const LEN: usize = size_of::(); 32 | } 33 | 34 | impl Default for JobContract { 35 | #[inline] 36 | fn default() -> JobContract { 37 | JobContract { 38 | contract_id: "".to_string(), 39 | employer: Pubkey::default(), 40 | employee: Pubkey::default(), 41 | employer_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 42 | employee_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 43 | start_time: 0, 44 | deadline: 0, 45 | amount: 0, 46 | dispute: 0, 47 | split: false, 48 | employer_satisfied: false, 49 | employer_approved: false, 50 | employee_approved: false, 51 | admin_approved: false, 52 | status: JobContractStatus::NoExist, 53 | featured: false, 54 | featured_day: 0, 55 | } 56 | } 57 | } 58 | 59 | #[derive(Eq, AnchorSerialize, AnchorDeserialize, Clone, PartialEq)] 60 | pub enum JobContractStatus { 61 | Initialized, 62 | NotInitialized, 63 | NoExist, 64 | Created, 65 | Active, 66 | Accepted, 67 | Pending, 68 | Dispute, 69 | Completed, 70 | Listed 71 | } 72 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/update_worked_hour.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn update_worked_hour( 19 | ctx: Context, 20 | contract_id: String, 21 | week_worked_hour: u32, 22 | ) -> Result<()> { 23 | msg!("Updating weekly worked hours on seller side!"); 24 | let contract = &mut ctx.accounts.contract; 25 | 26 | // Check if the signer is a correct seller 27 | require_keys_eq!(ctx.accounts.seller.key(), contract.seller, GigContractError::InvalidActivator); 28 | 29 | // Check if the contract is not paused 30 | require!(contract.paused == false, GigContractError::HourlyContractPaused); 31 | 32 | // Check if the contract is not ended 33 | require!(contract.status != HourlyContractStatus::Ended, GigContractError::HourlyContractEnded); 34 | 35 | // Check if the contract is active. 36 | require!(contract.status == HourlyContractStatus::Active, GigContractError::CantRelease); 37 | 38 | // Check if the worked hour is less than weekly_hours_limit 39 | require!(contract.weekly_hours_limit >= week_worked_hour, GigContractError::WeeklyHoursLimitError); 40 | 41 | contract.status = HourlyContractStatus::ReadyToPay; 42 | contract.week_worked_hour = week_worked_hour; 43 | 44 | msg!("Updated weekly worked hours successfully!"); 45 | Ok(()) 46 | } 47 | 48 | #[derive(Accounts)] 49 | #[instruction(contract_id: String)] 50 | pub struct UpdateWorkedHourContext<'info> { 51 | #[account(mut)] 52 | pub seller: Signer<'info>, 53 | 54 | #[account( 55 | mut, 56 | seeds = [ 57 | CONTRACT_SEED.as_bytes(), 58 | &contract_id.as_bytes() 59 | ], 60 | bump, 61 | )] 62 | pub contract: Account<'info, HourlyContract>, 63 | 64 | pub system_program: Program<'info, System>, 65 | } 66 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/admin_withdraw_job.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{Token, Mint, TokenAccount, Transfer as SplTransfer}; 3 | use crate::state::contract::*; 4 | use crate::constants::*; 5 | use crate::errors::*; 6 | use crate::state::job_contract::*; 7 | use crate::state::hourly_contract::*; 8 | 9 | 10 | pub fn admin_withdraw_job_contract(ctx: Context, contract_id: String) -> Result<()> { 11 | // Log the contract_id for debugging 12 | msg!("Contract ID: {}", contract_id); 13 | 14 | let cpi_accounts = anchor_spl::token::Transfer { 15 | from: ctx.accounts.contract_ata.to_account_info(), 16 | to: ctx.accounts.withdraw_address.to_account_info(), 17 | authority: ctx.accounts.contract.to_account_info(), 18 | }; 19 | 20 | let amount = ctx.accounts.contract_ata.amount; 21 | 22 | let cpi_program = ctx.accounts.token_program.to_account_info(); 23 | let seeds = &[b"gig_contract".as_ref(), &contract_id.as_bytes(), &[ctx.bumps.contract]]; 24 | 25 | // Log the derived address for debugging 26 | let derived_address = Pubkey::create_program_address(seeds, ctx.program_id).expect("Failed to create derived address"); 27 | msg!("Derived Address: {}", derived_address); 28 | 29 | let signer = &[&seeds[..]]; 30 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 31 | anchor_spl::token::transfer(cpi_ctx, amount)?; 32 | 33 | Ok(()) 34 | } 35 | 36 | #[derive(Accounts)] 37 | #[instruction(contract_id: String)] 38 | pub struct AdminWithdrawJobContractContext<'info> { 39 | #[account( 40 | mut, 41 | seeds = [ 42 | CONTRACT_SEED.as_bytes(), 43 | &contract_id.as_bytes() 44 | ], 45 | bump, 46 | )] 47 | pub contract: Account<'info, JobContract>, 48 | 49 | #[account(mut)] 50 | pub admin: Signer<'info>, 51 | 52 | /// CHECK: this is safe account. no need to check 53 | pub pay_token_mint: AccountInfo<'info>, 54 | 55 | #[account(mut)] 56 | pub contract_ata: Account<'info, TokenAccount>, 57 | 58 | pub token_program: Program<'info, Token>, 59 | 60 | #[account( 61 | mut, 62 | constraint = withdraw_address.owner == ADMIN_ADDRESS.key() 63 | )] 64 | pub withdraw_address: Account<'info, TokenAccount>, 65 | 66 | pub system_program: Program<'info, System>, 67 | pub rent: Sysvar<'info, Rent> 68 | } -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/state/hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use std::mem::size_of; 3 | 4 | use crate::constants::*; 5 | use crate::errors::{ 6 | GigContractError 7 | }; 8 | 9 | #[account] 10 | pub struct HourlyContract { 11 | pub buyer: Pubkey, 12 | pub seller: Pubkey, 13 | pub buyer_referral: Pubkey, 14 | pub seller_referral: Pubkey, 15 | pub contract_id: String, 16 | pub start_time: u32, 17 | pub deadline: u32, 18 | pub dispute: u64, 19 | pub split: bool, 20 | pub seller_satisfied: bool, // regarding split decision 21 | pub buyer_approved: bool, 22 | pub seller_approved: bool, 23 | pub admin_approved: bool, 24 | pub paused: bool, 25 | pub status: HourlyContractStatus, 26 | 27 | pub hourly_rate: u32, 28 | pub week_worked_hour: u32, // will be set by freelancer 29 | pub total_worked_hour: u32, // will be updated when doing payment 30 | pub weekly_hours_limit: u32, // will be set by client 31 | } 32 | 33 | impl HourlyContract { 34 | pub const LEN: usize = size_of::(); 35 | } 36 | 37 | impl Default for HourlyContract { 38 | #[inline] 39 | fn default() -> HourlyContract { 40 | HourlyContract { 41 | contract_id: "".to_string(), 42 | buyer: Pubkey::default(), 43 | seller: Pubkey::default(), 44 | buyer_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 45 | seller_referral: anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"), 46 | start_time: 0, 47 | deadline: 0, 48 | dispute: 0, 49 | split: false, 50 | seller_satisfied: false, 51 | buyer_approved: false, 52 | seller_approved: false, 53 | admin_approved: false, 54 | paused: false, 55 | status: HourlyContractStatus::NoExist, 56 | 57 | hourly_rate: 0, 58 | week_worked_hour: 0, // will be set by freelancer 59 | total_worked_hour: 0, // will be updated when doing payment 60 | weekly_hours_limit: 0, // will be set by client 61 | } 62 | } 63 | } 64 | 65 | #[derive(Eq, AnchorSerialize, AnchorDeserialize, Clone, PartialEq)] 66 | pub enum HourlyContractStatus { 67 | NoExist, 68 | Created, 69 | Active, 70 | Accepted, 71 | ReadyToPay, 72 | Paid, 73 | Pending, 74 | Dispute, 75 | Completed, 76 | Ended, 77 | } 78 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/buyer_approve.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | ADMIN_ADDRESS, 12 | PAY_TOKEN_MINT_ADDRESS 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn buyer_approve( 20 | ctx: Context, 21 | contract_id: String, 22 | split: bool 23 | ) -> Result<()> { 24 | msg!("Releasing funds on buyer side!"); 25 | 26 | let contract = &mut ctx.accounts.contract; 27 | 28 | // Check if the signer is a correct buyer 29 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidBuyer); 30 | 31 | // Check if the contract is Active or pending. 32 | require!(contract.status == ContractStatus::Active || contract.status == ContractStatus::Pending, GigContractError::CantRelease); 33 | 34 | let token_program = &ctx.accounts.token_program; 35 | let source = &ctx.accounts.contract_ata; 36 | let seller_destination = &ctx.accounts.seller_ata; 37 | let buyer_destination = &ctx.accounts.buyer_ata; 38 | let admin_destination = &ctx.accounts.admin_ata; 39 | 40 | contract.status = ContractStatus::Pending; 41 | contract.buyer_approved = true; 42 | contract.split = split; 43 | 44 | let total_balance = source.amount; 45 | 46 | msg!("Funds released by buyer successfully!"); 47 | Ok(()) 48 | } 49 | 50 | #[derive(Accounts)] 51 | #[instruction(contract_id: String)] 52 | pub struct BuyerApproveContext<'info> { 53 | #[account(mut)] 54 | pub buyer: Signer<'info>, 55 | 56 | #[account( 57 | mut, 58 | seeds = [ 59 | CONTRACT_SEED.as_bytes(), 60 | &contract_id.as_bytes() 61 | ], 62 | bump, 63 | )] 64 | pub contract: Account<'info, Contract>, 65 | 66 | #[account( 67 | mut, 68 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 69 | associated_token::authority = contract.seller, 70 | )] 71 | pub seller_ata: Account<'info, TokenAccount>, 72 | 73 | #[account( 74 | mut, 75 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 76 | associated_token::authority = contract.buyer, 77 | )] 78 | pub buyer_ata: Account<'info, TokenAccount>, 79 | 80 | #[account( 81 | mut, 82 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 83 | associated_token::authority = ADMIN_ADDRESS, 84 | )] 85 | pub admin_ata: Account<'info, TokenAccount>, 86 | 87 | 88 | #[account( 89 | mut, 90 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 91 | associated_token::authority = contract, 92 | )] 93 | pub contract_ata: Account<'info, TokenAccount>, 94 | 95 | pub token_program: Program<'info, Token>, 96 | pub associated_token_program: Program<'info, AssociatedToken>, 97 | pub system_program: Program<'info, System>, 98 | } 99 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/accept_contract_on_buyer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn accept_contract_on_buyer( 19 | ctx: Context, 20 | contract_id: String, 21 | ) -> Result<()> { 22 | msg!("Accepting contact on buyer side!"); 23 | let contract = &mut ctx.accounts.contract; 24 | 25 | // Check if the signer is a correct buyer 26 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidAcceptor); 27 | 28 | // Check if the contract is created. 29 | require!(contract.status == ContractStatus::Created, GigContractError::CantAccept); 30 | 31 | let token_program = &ctx.accounts.token_program; 32 | let authority = &ctx.accounts.buyer; 33 | let source = &ctx.accounts.buyer_ata; 34 | let destination = &ctx.accounts.contract_ata; 35 | 36 | contract.status = ContractStatus::Accepted; 37 | 38 | if let Some(buyer_referral) = &ctx.accounts.buyer_referral { 39 | msg!("buyer_referral provided: {}", buyer_referral.key()); 40 | contract.buyer_referral = buyer_referral.key(); 41 | } 42 | 43 | // Transfer paytoken(amount + dispute) to the contract account 44 | token::transfer( 45 | CpiContext::new( 46 | token_program.to_account_info(), 47 | SplTransfer { 48 | from: source.to_account_info().clone(), 49 | to: destination.to_account_info().clone(), 50 | authority: authority.to_account_info().clone(), 51 | }, 52 | ), 53 | (contract.amount + contract.dispute).try_into().unwrap(), 54 | )?; 55 | 56 | msg!("Contract accepted successfully!"); 57 | Ok(()) 58 | } 59 | 60 | #[derive(Accounts)] 61 | #[instruction(contract_id: String)] 62 | pub struct AcceptContractOnBuyerContext<'info> { 63 | #[account(mut)] 64 | pub buyer: Signer<'info>, 65 | 66 | #[account( 67 | mut, 68 | seeds = [ 69 | CONTRACT_SEED.as_bytes(), 70 | &contract_id.as_bytes() 71 | ], 72 | bump, 73 | )] 74 | pub contract: Account<'info, Contract>, 75 | 76 | #[account( 77 | mut, 78 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 79 | associated_token::authority = buyer, 80 | )] 81 | pub buyer_ata: Account<'info, TokenAccount>, 82 | 83 | // Referral is optional 84 | pub buyer_referral: Option>, 85 | 86 | #[account( 87 | mut, 88 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 89 | associated_token::authority = contract, 90 | )] 91 | pub contract_ata: Account<'info, TokenAccount>, 92 | 93 | pub token_program: Program<'info, Token>, 94 | pub associated_token_program: Program<'info, AssociatedToken>, 95 | pub system_program: Program<'info, System>, 96 | } 97 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/pay_worked_hour.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn pay_worked_hour( 19 | ctx: Context, 20 | contract_id: String, 21 | amount: u64 22 | ) -> Result<()> { 23 | msg!("Paying weekly worked hours on buyer side!"); 24 | let contract = &mut ctx.accounts.contract; 25 | 26 | let token_program = &ctx.accounts.token_program; 27 | let authority = &ctx.accounts.buyer; 28 | let source = &ctx.accounts.buyer_ata; 29 | let destination = &ctx.accounts.contract_ata; 30 | 31 | // Check if the signer is a correct seller 32 | require_keys_eq!(ctx.accounts.buyer.key(), contract.buyer, GigContractError::InvalidActivator); 33 | 34 | // Check if the contract is ReadyToPay. 35 | require!(contract.status == HourlyContractStatus::ReadyToPay, GigContractError::HourlyGigPayError); 36 | 37 | // powi(10.0, 6) for USDC, powi(10.0, 8) for BPT for test 38 | require!(amount == (contract.week_worked_hour as f64 * contract.hourly_rate as f64 * f64::powi(10.0, 6)).round() as u64 , GigContractError::PayAmountError); 39 | 40 | contract.status = HourlyContractStatus::Paid; 41 | contract.total_worked_hour += contract.week_worked_hour; 42 | contract.week_worked_hour = 0; 43 | 44 | // Transfer paytoken(amount) which is calculated by hourly rate * worked hour to the contract account 45 | token::transfer( 46 | CpiContext::new( 47 | token_program.to_account_info(), 48 | SplTransfer { 49 | from: source.to_account_info().clone(), 50 | to: destination.to_account_info().clone(), 51 | authority: authority.to_account_info().clone(), 52 | }, 53 | ), 54 | amount, 55 | )?; 56 | 57 | msg!("Paid weekly worked hours successfully!"); 58 | Ok(()) 59 | } 60 | 61 | #[derive(Accounts)] 62 | #[instruction(contract_id: String)] 63 | pub struct PayWorkedHourContext<'info> { 64 | #[account(mut)] 65 | pub buyer: Signer<'info>, 66 | 67 | #[account( 68 | mut, 69 | seeds = [ 70 | CONTRACT_SEED.as_bytes(), 71 | &contract_id.as_bytes() 72 | ], 73 | bump, 74 | )] 75 | pub contract: Account<'info, HourlyContract>, 76 | 77 | pub pay_token_mint: Account<'info, Mint>, 78 | 79 | #[account( 80 | mut, 81 | associated_token::mint = pay_token_mint, 82 | associated_token::authority = buyer, 83 | )] 84 | pub buyer_ata: Account<'info, TokenAccount>, 85 | 86 | #[account( 87 | init_if_needed, 88 | payer = buyer, 89 | associated_token::mint = pay_token_mint, 90 | associated_token::authority = contract, 91 | )] 92 | pub contract_ata: Account<'info, TokenAccount>, 93 | 94 | pub token_program: Program<'info, Token>, 95 | pub associated_token_program: Program<'info, AssociatedToken>, 96 | pub system_program: Program<'info, System>, 97 | } 98 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/activate_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn activate_contract( 19 | ctx: Context, 20 | contract_id: String, 21 | with_dispute: bool, 22 | ) -> Result<()> { 23 | msg!("Activating contact on seller side!"); 24 | let contract = &mut ctx.accounts.contract; 25 | 26 | // Check if the signer is a correct seller 27 | require_keys_eq!(ctx.accounts.seller.key(), contract.seller, GigContractError::InvalidActivator); 28 | 29 | // Check if the contract is Created or Accepted. 30 | require!(contract.status == ContractStatus::Created || contract.status == ContractStatus::Accepted, GigContractError::CantActivate); 31 | 32 | let token_program = &ctx.accounts.token_program; 33 | let authority = &ctx.accounts.seller; 34 | let source = &ctx.accounts.seller_ata; 35 | let destination = &ctx.accounts.contract_ata; 36 | 37 | contract.status = ContractStatus::Active; 38 | 39 | if let Some(seller_referral) = &ctx.accounts.seller_referral { 40 | msg!("seller_referral provided: {}", seller_referral.key()); 41 | contract.seller_referral = seller_referral.key(); 42 | } 43 | 44 | if with_dispute == true { 45 | // Transfer paytoken(dispute) to the contract account 46 | token::transfer( 47 | CpiContext::new( 48 | token_program.to_account_info(), 49 | SplTransfer { 50 | from: source.to_account_info().clone(), 51 | to: destination.to_account_info().clone(), 52 | authority: authority.to_account_info().clone(), 53 | }, 54 | ), 55 | contract.dispute, 56 | )?; 57 | } 58 | 59 | msg!("Contract activated successfully!"); 60 | Ok(()) 61 | } 62 | 63 | #[derive(Accounts)] 64 | #[instruction(contract_id: String)] 65 | pub struct ActivateContractContext<'info> { 66 | #[account(mut)] 67 | pub seller: Signer<'info>, 68 | 69 | #[account( 70 | mut, 71 | seeds = [ 72 | CONTRACT_SEED.as_bytes(), 73 | &contract_id.as_bytes() 74 | ], 75 | bump, 76 | )] 77 | pub contract: Account<'info, Contract>, 78 | 79 | #[account( 80 | mut, 81 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 82 | associated_token::authority = seller, 83 | )] 84 | pub seller_ata: Account<'info, TokenAccount>, 85 | 86 | // Referral is optional 87 | pub seller_referral: Option>, 88 | 89 | #[account( 90 | mut, 91 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 92 | associated_token::authority = contract, 93 | )] 94 | pub contract_ata: Account<'info, TokenAccount>, 95 | 96 | pub token_program: Program<'info, Token>, 97 | pub associated_token_program: Program<'info, AssociatedToken>, 98 | pub system_program: Program<'info, System>, 99 | } 100 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/activate_hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS 12 | }; 13 | use crate::errors::{ 14 | GigContractError 15 | }; 16 | 17 | 18 | pub fn activate_hourly_contract( 19 | ctx: Context, 20 | contract_id: String, 21 | with_dispute: bool, 22 | ) -> Result<()> { 23 | msg!("Activating hourly contact on seller side!"); 24 | let contract = &mut ctx.accounts.contract; 25 | 26 | // Check if the signer is a correct seller 27 | require_keys_eq!(ctx.accounts.seller.key(), contract.seller, GigContractError::InvalidActivator); 28 | 29 | // Check if the contract is Created or Accepted. 30 | require!(contract.status == HourlyContractStatus::Created || contract.status == HourlyContractStatus::Accepted, GigContractError::CantActivate); 31 | 32 | let token_program = &ctx.accounts.token_program; 33 | let authority = &ctx.accounts.seller; 34 | let source = &ctx.accounts.seller_ata; 35 | let destination = &ctx.accounts.contract_ata; 36 | 37 | contract.status = HourlyContractStatus::Active; 38 | 39 | if let Some(seller_referral) = &ctx.accounts.seller_referral { 40 | msg!("seller_referral provided: {}", seller_referral.key()); 41 | contract.seller_referral = seller_referral.key(); 42 | } 43 | 44 | if with_dispute == true { 45 | // Transfer paytoken(dispute) to the contract account 46 | token::transfer( 47 | CpiContext::new( 48 | token_program.to_account_info(), 49 | SplTransfer { 50 | from: source.to_account_info().clone(), 51 | to: destination.to_account_info().clone(), 52 | authority: authority.to_account_info().clone(), 53 | }, 54 | ), 55 | contract.dispute, 56 | )?; 57 | } 58 | 59 | msg!("Contract activated successfully!"); 60 | Ok(()) 61 | } 62 | 63 | #[derive(Accounts)] 64 | #[instruction(contract_id: String)] 65 | pub struct ActivateHourlyContractContext<'info> { 66 | #[account(mut)] 67 | pub seller: Signer<'info>, 68 | 69 | #[account( 70 | mut, 71 | seeds = [ 72 | CONTRACT_SEED.as_bytes(), 73 | &contract_id.as_bytes() 74 | ], 75 | bump, 76 | )] 77 | pub contract: Account<'info, HourlyContract>, 78 | 79 | #[account( 80 | mut, 81 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 82 | associated_token::authority = seller, 83 | )] 84 | pub seller_ata: Account<'info, TokenAccount>, 85 | 86 | // Referral is optional 87 | pub seller_referral: Option>, 88 | 89 | #[account( 90 | mut, 91 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 92 | associated_token::authority = contract, 93 | )] 94 | pub contract_ata: Account<'info, TokenAccount>, 95 | 96 | pub token_program: Program<'info, Token>, 97 | pub associated_token_program: Program<'info, AssociatedToken>, 98 | pub system_program: Program<'info, System>, 99 | } 100 | -------------------------------------------------------------------------------- /tests/job-listing-test-js.test: -------------------------------------------------------------------------------- 1 | const anchor = require('@project-serum/anchor'); 2 | const {ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddress, Token} = require("@solana/spl-token"); 3 | const uuid = require("uuid"); 4 | const createInitAccountInstruction = require("@solana/spl-token"); 5 | const { Connection, PublicKey, Keypair, SystemProgram, Transaction } = require('@solana/web3.js'); 6 | 7 | const secret = require('/home/god/.config/solana/id.json'); 8 | // const GigBasicContract = require("../target/types/gig_basic_contract"); 9 | const newIDL = require("../target/idl/gig_basic_contract.json"); 10 | 11 | let contractId = "a4fc3529"; 12 | 13 | 14 | 15 | // Set up the connection to the Devnet 16 | const connection = new Connection('https://api.devnet.solana.com', 'confirmed'); 17 | 18 | // Load your wallet 19 | const wallet = Keypair.fromSecretKey(new Uint8Array(secret)); // Replace with your secret key 20 | 21 | // Define the program ID of your deployed contract 22 | const programId = new PublicKey('4Wijqow1TA9kSs3o9XkYsb817abaTmNoqe6pohoNoqXx'); // Replace with your program ID 23 | 24 | // Create an Anchor provider 25 | const provider = new anchor.AnchorProvider(connection, wallet, anchor.AnchorProvider.defaultOptions()); 26 | console.log("------------- getting provider success -------------") 27 | anchor.setProvider(provider); 28 | 29 | async function TestJobListing() { 30 | // Load the program 31 | const idl = anchor.Program.fetchIdl(programId, provider).then((data) => { 32 | console.log("here-------", data) 33 | }); 34 | // const program = new anchor.Program(newIDL, programId); // Replace `idl` with your program's IDL 35 | 36 | // Generate a new contract ID 37 | contractId = uuid.v4().slice(0, 8); // Replace with a unique contract ID 38 | 39 | // Create associated token accounts if necessary 40 | const employerAta = await createAssociatedTokenAccount(wallet.publicKey, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 41 | const contractAta = await createAssociatedTokenAccount(programId, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 42 | 43 | // Call the job_listing_with_one_fee_employer function 44 | try { 45 | const tx = await program.rpc.jobListingWithOneFeeEmployer(contractId, { 46 | accounts: { 47 | employer: wallet.publicKey, 48 | jobContract: contractAta, // Replace with the actual job contract account 49 | employerAta: employerAta, 50 | contractAta: contractAta, 51 | employerTokenAccount: employerAta, 52 | programTokenAccount: contractAta, 53 | tokenProgram: TOKEN_PROGRAM_ID, 54 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 55 | systemProgram: SystemProgram.programId, 56 | }, 57 | }); 58 | 59 | console.log("Transaction successful with signature:", tx); 60 | } catch (error) { 61 | console.error("Transaction failed:", error); 62 | } 63 | } 64 | 65 | async function createAssociatedTokenAccount(owner, mint) { 66 | const associatedToken = await PublicKey.findProgramAddress( 67 | [ 68 | owner.toBuffer(), 69 | ], 70 | ASSOCIATED_TOKEN_PROGRAM_ID 71 | ); 72 | 73 | const transaction = new Transaction().add( 74 | SystemProgram.createAccount({ 75 | fromPubkey: owner, 76 | newAccountPubkey: associatedToken[0], 77 | lamports: await connection.getMinimumBalanceForRentExemption(165), 78 | space: 165, 79 | programId: TOKEN_PROGRAM_ID, 80 | }), 81 | Token.createInitAccountInstruction( 82 | TOKEN_PROGRAM_ID, 83 | mint, 84 | associatedToken[0], 85 | owner 86 | ) 87 | ); 88 | 89 | await provider.sendAndConfirm(transaction, [wallet]); 90 | return associatedToken[0]; 91 | } 92 | 93 | // Run the test 94 | TestJobListing(); -------------------------------------------------------------------------------- /tests/job-listing-test.test: -------------------------------------------------------------------------------- 1 | import * as anchor from '@project-serum/anchor'; 2 | import { Program, Wallet } from '@project-serum/anchor'; 3 | // import { Program, Wallet } from "@coral-xyz/anchor@0.30.1"; 4 | import { TOKEN_PROGRAM_ID, Token, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; 5 | import { Connection, PublicKey, Keypair, SystemProgram, Transaction } from '@solana/web3.js'; 6 | import { v4 as uuid } from 'uuid'; 7 | import { GigBasicContract } from '../target/types/gig_basic_contract'; 8 | 9 | const secret = require('~/.config/solana/id.json'); 10 | let contractId: string = "a4fc3529"; 11 | 12 | // Set up the connection to the Devnet 13 | const connection = new Connection('https://api.devnet.solana.com', 'confirmed'); 14 | 15 | // Load your wallet 16 | const wallet = Keypair.fromSecretKey(new Uint8Array(secret)); // Replace with your secret key 17 | 18 | // Define the program ID of your deployed contract 19 | const programId = new PublicKey('4Wijqow1TA9kSs3o9XkYsb817abaTmNoqe6pohoNoqXx'); // Replace with your program ID 20 | 21 | // Create an Anchor provider 22 | const provider = new anchor.AnchorProvider(connection, wallet, anchor.AnchorProvider.defaultOptions()); 23 | console.log("------------- getting provider success -------------"); 24 | anchor.setProvider(provider); 25 | 26 | async function TestJobListing(): Promise { 27 | // Load the program 28 | // const program = new anchor.Program(GigBasicContract, programId); // Replace `GigBasicContract` with your program's IDL 29 | const program = anchor.workspace 30 | .GigBasicContract as Program; 31 | // Generate a new contract ID 32 | contractId = uuid().slice(0, 8); // Replace with a unique contract ID 33 | 34 | // Create associated token accounts if necessary 35 | const employerAta = await createAssociatedTokenAccount(wallet.publicKey, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 36 | const contractAta = await createAssociatedTokenAccount(programId, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 37 | 38 | // Call the job_listing_with_one_fee_employer function 39 | try { 40 | const tx = await program.rpc.jobListingWithOneFeeEmployer(contractId, { 41 | accounts: { 42 | employer: wallet.publicKey, 43 | jobContract: contractAta, // Replace with the actual job contract account 44 | employerAta: employerAta, 45 | contractAta: contractAta, 46 | employerTokenAccount: employerAta, 47 | programTokenAccount: contractAta, 48 | tokenProgram: TOKEN_PROGRAM_ID, 49 | associatedTokenProgram: TOKEN_PROGRAM_ID, // Use the correct associated token program ID 50 | systemProgram: SystemProgram.programId, 51 | }, 52 | }); 53 | 54 | console.log("Transaction successful with signature:", tx); 55 | } catch (error) { 56 | console.error("Transaction failed:", error); 57 | } 58 | } 59 | 60 | async function createAssociatedTokenAccount(owner: PublicKey, mint: string): Promise { 61 | const mintPublicKey = new PublicKey(mint); 62 | const associatedToken = await PublicKey.findProgramAddress( 63 | [ 64 | owner.toBuffer(), 65 | TOKEN_PROGRAM_ID.toBuffer(), 66 | mintPublicKey.toBuffer(), 67 | ], 68 | TOKEN_PROGRAM_ID // Use the correct associated token program ID 69 | ); 70 | 71 | const transaction = new Transaction().add( 72 | SystemProgram.createAccount({ 73 | fromPubkey: owner, 74 | newAccountPubkey: associatedToken[0], 75 | lamports: await connection.getMinimumBalanceForRentExemption(165), 76 | space: 165, 77 | programId: TOKEN_PROGRAM_ID, 78 | }), 79 | Token.createInitAccountInstruction( 80 | TOKEN_PROGRAM_ID, 81 | mintPublicKey, 82 | associatedToken[0], 83 | owner 84 | ) 85 | ); 86 | 87 | await provider.sendAndConfirm(transaction, [wallet]); 88 | return associatedToken[0]; 89 | } 90 | 91 | // Run the test 92 | TestJobListing(); -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/start_contract_on_seller.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS, 12 | DECIMAL 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn start_contract_on_seller( 20 | ctx: Context, 21 | contract_id: String, 22 | amount: u64, 23 | dispute: u64, // $0.5 for now 24 | deadline: u32, 25 | ) -> Result<()> { 26 | msg!("Creating a new contract with the following Id: {}", contract_id); 27 | 28 | require_keys_eq!(ctx.accounts.pay_token_mint.key(), PAY_TOKEN_MINT_ADDRESS, GigContractError::PayTokenMintError); 29 | 30 | // Check if the contract is pending which means one of two parties approved. 31 | // powi(10.0, 6) for USDC, powi(10.0, 8) for BPT for test 32 | require!(dispute == (0.5 * f64::powi(10.0, 6)).round() as u64 , GigContractError::InvalidDisputeAmount); 33 | 34 | let contract = &mut ctx.accounts.contract; 35 | let current_timestamp = Clock::get()?.unix_timestamp as u32; 36 | let token_program = &ctx.accounts.token_program; 37 | let authority = &ctx.accounts.seller; 38 | let source = &ctx.accounts.seller_ata; 39 | let destination = &ctx.accounts.contract_ata; 40 | 41 | contract.contract_id = contract_id; 42 | contract.buyer = ctx.accounts.buyer.key(); 43 | contract.seller = ctx.accounts.seller.key(); 44 | contract.start_time = current_timestamp; 45 | contract.amount = amount; 46 | contract.dispute = dispute; 47 | contract.deadline = deadline; 48 | contract.status = ContractStatus::Created; 49 | 50 | contract.buyer_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 51 | contract.seller_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 52 | 53 | if let Some(seller_referral) = &ctx.accounts.seller_referral { 54 | msg!("seller_referral provided: {}", seller_referral.key()); 55 | contract.seller_referral = seller_referral.key(); 56 | } 57 | 58 | // Transfer paytoken(dispute) to the contract account 59 | token::transfer( 60 | CpiContext::new( 61 | token_program.to_account_info(), 62 | SplTransfer { 63 | from: source.to_account_info().clone(), 64 | to: destination.to_account_info().clone(), 65 | authority: authority.to_account_info().clone(), 66 | }, 67 | ), 68 | dispute, 69 | )?; 70 | 71 | msg!("New contract created successfully on seller side!"); 72 | Ok(()) 73 | } 74 | 75 | #[derive(Accounts)] 76 | #[instruction(contract_id: String)] 77 | pub struct StartContractOnSellerContext<'info> { 78 | #[account(mut)] 79 | pub seller: Signer<'info>, 80 | 81 | #[account( 82 | init, 83 | seeds = [ 84 | CONTRACT_SEED.as_bytes(), 85 | &contract_id.as_bytes() 86 | ], 87 | payer = seller, 88 | bump, 89 | space = size_of::() + 8, 90 | )] 91 | pub contract: Account<'info, Contract>, 92 | 93 | pub buyer: SystemAccount<'info>, 94 | 95 | // Referral is optional 96 | pub seller_referral: Option>, 97 | 98 | pub pay_token_mint: Account<'info, Mint>, 99 | 100 | #[account( 101 | mut, 102 | associated_token::mint = pay_token_mint, 103 | associated_token::authority = seller, 104 | )] 105 | pub seller_ata: Account<'info, TokenAccount>, 106 | 107 | #[account( 108 | init_if_needed, 109 | payer = seller, 110 | associated_token::mint = pay_token_mint, 111 | associated_token::authority = contract, 112 | )] 113 | pub contract_ata: Account<'info, TokenAccount>, 114 | 115 | pub token_program: Program<'info, Token>, 116 | pub associated_token_program: Program<'info, AssociatedToken>, 117 | pub system_program: Program<'info, System>, 118 | pub rent: Sysvar<'info, Rent> 119 | } 120 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/job_listing_with_one_fee_employer(usdc fee).rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{self, Mint, Token, TokenAccount, Transfer as SplTransfer}, 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::job_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS, 12 | }; 13 | use crate::errors::{ 14 | GigContractError, 15 | }; 16 | 17 | pub fn job_listing_with_fees_employer( 18 | ctx: Context, 19 | contract_id: String, 20 | with_dispute: bool, 21 | ) -> Result<()> { 22 | msg!("Listing Job with $1 fee on employer side!"); 23 | 24 | let contract = &mut ctx.accounts.contract; 25 | 26 | // Check if the signer is the correct employer 27 | require_keys_eq!(ctx.accounts.employer.key(), contract.employer, GigContractError::InvalidActivator); 28 | 29 | // Check if the contract is not ended. 30 | // require!(contract.status != JobContractStatus::Created, GigContractError::HourlyContractEnded); 31 | 32 | // Define the fees 33 | let listing_fee = 1_000_000; // 1 USDC = 0.000001 micro USDC 34 | let dispute_fee = 1_000_000; // Same assumption for dispute fee 35 | 36 | // Define source address and destination address 37 | let employer_destination = &ctx.accounts.employer_ata; 38 | let contract_destination = &ctx.accounts.contract_ata; 39 | let authority = &ctx.accounts.employer; 40 | 41 | // Transfer listing fee from employer to the program's account 42 | let cpi_accounts = SplTransfer { 43 | from: employer_destination.to_account_info().clone(), 44 | to: contract_destination.to_account_info().clone(), 45 | authority: authority.to_account_info().clone(), 46 | }; 47 | 48 | let cpi_program = ctx.accounts.token_program.to_account_info(); 49 | 50 | // Transfer listing fee 51 | let transfer_listing_fee_ctx = CpiContext::new(cpi_program.clone(), cpi_accounts); 52 | SplTransfer::transfer(transfer_listing_fee_ctx, listing_fee)?; 53 | 54 | msg!("Transferred listing fee of 1 USDC!"); 55 | 56 | if with_dispute { 57 | // Transfer dispute fee if applicable 58 | let dispute_cpi_accounts = SplTransfer { 59 | from: ctx.accounts.employer_token_account.to_account_info(), 60 | to: ctx.accounts.program_token_account.to_account_info(), 61 | authority: ctx.accounts.employer.to_account_info(), 62 | }; 63 | 64 | let dispute_transfer_ctx = CpiContext::new(cpi_program.clone(), dispute_cpi_accounts); 65 | SplTransfer::transfer(dispute_transfer_ctx, dispute_fee)?; 66 | 67 | msg!("Transferred dispute fee of 1 USDC!"); 68 | } 69 | 70 | // Update contract status or any other necessary fields 71 | contract.status = JobContractStatus::Listed; // Example status update 72 | 73 | msg!("Job listed successfully!"); 74 | 75 | Ok(()) 76 | } 77 | 78 | #[derive(Accounts)] 79 | #[instruction(contract_id: String)] 80 | pub struct JobListingWithFeesEmployerContext<'info> { 81 | #[account(mut)] 82 | pub employer: Signer<'info>, 83 | 84 | #[account( 85 | mut, 86 | seeds = [ 87 | CONTRACT_SEED.as_bytes(), 88 | &contract_id.as_bytes() 89 | ], 90 | bump, 91 | )] 92 | pub contract: Account<'info, HourlyContract>, 93 | 94 | #[account( 95 | mut, 96 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 97 | associated_token::authority = contract.employer, 98 | )] 99 | pub employer_ata: Account<'info, TokenAccount>, 100 | 101 | #[account( 102 | mut, 103 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 104 | associated_token::authority = contract, 105 | )] 106 | pub contract_ata: Account<'info, TokenAccount>, 107 | 108 | pub employer_token_account: Account<'info, TokenAccount>, // Employer's token account 109 | 110 | #[account(mut)] 111 | pub program_token_account: Account<'info, TokenAccount>, // Program's token account to receive fees 112 | 113 | pub token_program: Program<'info, Token>, 114 | pub associated_token_program: Program<'info, AssociatedToken>, 115 | pub system_program: Program<'info, System>, 116 | } -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/start_contract_on_buyer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS, 12 | DECIMAL 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn start_contract_on_buyer( 20 | ctx: Context, 21 | contract_id: String, 22 | amount: u64, 23 | dispute: u64, // $0.5 for now 24 | deadline: u32, 25 | ) -> Result<()> { 26 | msg!("Creating a new contract with the following Id: {}", contract_id); 27 | 28 | require_keys_eq!(ctx.accounts.pay_token_mint.key(), PAY_TOKEN_MINT_ADDRESS, GigContractError::PayTokenMintError); 29 | 30 | // Check if the contract is pending which means one of two parties approved. 31 | // powi(10.0, 6) for USDC, powi(10.0, 8) for BPT for test 32 | require!(dispute == (0.5 * f64::powi(10.0, 6)).round() as u64 , GigContractError::InvalidDisputeAmount); 33 | 34 | let contract = &mut ctx.accounts.contract; 35 | let current_timestamp = Clock::get()?.unix_timestamp as u32; 36 | let token_program = &ctx.accounts.token_program; 37 | let authority = &ctx.accounts.buyer; 38 | let source = &ctx.accounts.buyer_ata; 39 | let destination = &ctx.accounts.contract_ata; 40 | 41 | contract.contract_id = contract_id; 42 | contract.buyer = ctx.accounts.buyer.key(); 43 | contract.seller = ctx.accounts.seller.key(); 44 | contract.start_time = current_timestamp; 45 | contract.amount = amount; 46 | contract.dispute = dispute; 47 | contract.deadline = deadline; 48 | contract.status = ContractStatus::Created; 49 | 50 | contract.buyer_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 51 | contract.seller_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 52 | 53 | if let Some(buyer_referral) = &ctx.accounts.buyer_referral { 54 | msg!("buyer_referral provided: {}", buyer_referral.key()); 55 | contract.buyer_referral = buyer_referral.key(); 56 | } 57 | 58 | // Transfer paytoken(amount + dispute) to the contract account 59 | token::transfer( 60 | CpiContext::new( 61 | token_program.to_account_info(), 62 | SplTransfer { 63 | from: source.to_account_info().clone(), 64 | to: destination.to_account_info().clone(), 65 | authority: authority.to_account_info().clone(), 66 | }, 67 | ), 68 | (amount + dispute).try_into().unwrap(), 69 | )?; 70 | 71 | msg!("New contract created successfully on buyer side!"); 72 | Ok(()) 73 | } 74 | 75 | #[derive(Accounts)] 76 | #[instruction(contract_id: String)] 77 | pub struct StartContractOnBuyerContext<'info> { 78 | #[account(mut)] 79 | pub buyer: Signer<'info>, 80 | 81 | #[account( 82 | init, 83 | seeds = [ 84 | CONTRACT_SEED.as_bytes(), 85 | &contract_id.as_bytes() 86 | ], 87 | payer = buyer, 88 | bump, 89 | space = size_of::() + 8, 90 | )] 91 | pub contract: Account<'info, Contract>, 92 | 93 | pub seller: SystemAccount<'info>, 94 | 95 | // Referral is optional 96 | pub buyer_referral: Option>, 97 | 98 | pub pay_token_mint: Account<'info, Mint>, 99 | 100 | #[account( 101 | mut, 102 | associated_token::mint = pay_token_mint, 103 | associated_token::authority = buyer, 104 | )] 105 | pub buyer_ata: Account<'info, TokenAccount>, 106 | 107 | #[account( 108 | init_if_needed, 109 | payer = buyer, 110 | associated_token::mint = pay_token_mint, 111 | associated_token::authority = contract, 112 | )] 113 | pub contract_ata: Account<'info, TokenAccount>, 114 | 115 | pub token_program: Program<'info, Token>, 116 | pub associated_token_program: Program<'info, AssociatedToken>, 117 | pub system_program: Program<'info, System>, 118 | pub rent: Sysvar<'info, Rent> 119 | } 120 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/job_listing_with_one_fee_employer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{self, Mint, Token, TokenAccount, Transfer as SplTransfer}, 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::job_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS, 12 | }; 13 | use crate::errors::{ 14 | GigContractError, 15 | }; 16 | 17 | pub fn job_listing_with_one_fee_employer( 18 | ctx: Context, 19 | contract_id: String, 20 | ) -> Result<()> { 21 | msg!("Listing Job with $1 fee on employer side!"); 22 | 23 | let job_contract = &mut ctx.accounts.job_contract; 24 | 25 | // // Check if the signer is the correct employer 26 | // require_keys_eq!(ctx.accounts.employer.key(), job_contract.employer, GigContractError::InvalidActivator); 27 | 28 | // Check if the contract is not ended. 29 | // require!(contract.status != JobContractStatus::Created, GigContractError::HourlyContractEnded); 30 | 31 | // Define the fees 32 | let listing_fee = 1_000_000; // 1 USDC = 0.000001 micro USDC 33 | let dispute_fee = 1_000_000; // Same assumption for dispute fee 34 | 35 | // Define source address and destination address 36 | let employer_destination = &ctx.accounts.employer_ata; 37 | let contract_destination = &ctx.accounts.contract_ata; 38 | let authority = &ctx.accounts.employer; 39 | let token_program = &ctx.accounts.token_program; 40 | 41 | token::transfer( 42 | CpiContext::new( 43 | token_program.to_account_info(), 44 | SplTransfer { 45 | from: employer_destination.to_account_info().clone(), 46 | to: contract_destination.to_account_info().clone(), 47 | authority: authority.to_account_info().clone(), 48 | }, 49 | ), 50 | listing_fee.try_into().unwrap(), 51 | )?; 52 | 53 | // // Transfer listing fee from employer to the program's account 54 | // let cpi_accounts = SplTransfer { 55 | // from: employer_destination.to_account_info().clone(), 56 | // to: contract_destination.to_account_info().clone(), 57 | // authority: authority.to_account_info().clone(), 58 | // }; 59 | 60 | // let cpi_program = ctx.accounts.token_program.to_account_info(); 61 | 62 | // // Transfer listing fee 63 | // let transfer_listing_fee_ctx = CpiContext::new(cpi_program.clone(), cpi_accounts); 64 | // SplTransfer::transfer(transfer_listing_fee_ctx, listing_fee)?; 65 | 66 | msg!("Transferred listing fee of 1 USDC!"); 67 | 68 | // if with_dispute { 69 | // // Transfer dispute fee if applicable 70 | // let dispute_cpi_accounts = SplTransfer { 71 | // from: ctx.accounts.employer_token_account.to_account_info(), 72 | // to: ctx.accounts.program_token_account.to_account_info(), 73 | // authority: ctx.accounts.employer.to_account_info(), 74 | // }; 75 | 76 | // let dispute_transfer_ctx = CpiContext::new(cpi_program.clone(), dispute_cpi_accounts); 77 | // SplTransfer::transfer(dispute_transfer_ctx, dispute_fee)?; 78 | 79 | // msg!("Transferred dispute fee of 1 USDC!"); 80 | // } 81 | 82 | // Update contract status or any other necessary fields 83 | job_contract.contract_id = contract_id; 84 | job_contract.status = JobContractStatus::Created; // Example status update 85 | 86 | msg!("Job listed successfully!"); 87 | 88 | Ok(()) 89 | } 90 | 91 | #[derive(Accounts)] 92 | #[instruction(contract_id: String)] 93 | pub struct JobListingWithFeesEmployerContext<'info> { 94 | #[account(mut)] 95 | pub employer: Signer<'info>, 96 | #[account( 97 | init, 98 | space = JobContract::LEN + 8, 99 | payer = employer, 100 | seeds = [ 101 | CONTRACT_SEED.as_bytes(), 102 | &contract_id.as_bytes() 103 | ], 104 | bump, 105 | )] 106 | pub job_contract: Account<'info, JobContract>, 107 | #[account(mut)] 108 | pub employer_ata: Account<'info, TokenAccount>, 109 | #[account(mut)] 110 | pub contract_ata: Account<'info, TokenAccount>, 111 | pub token_program: Program<'info, Token>, 112 | pub associated_token_program: Program<'info, AssociatedToken>, 113 | pub system_program: Program<'info, System>, 114 | } -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/start_hourly_contract_on_buyer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | PAY_TOKEN_MINT_ADDRESS, 12 | DECIMAL 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn start_hourly_contract_on_buyer( 20 | ctx: Context, 21 | contract_id: String, 22 | hourly_rate: u32, 23 | weekly_hours_limit: u32, 24 | dispute: u64, // $0.5 for now 25 | deadline: u32, 26 | ) -> Result<()> { 27 | msg!("Creating a new hourly contract with the following Id: {}", contract_id); 28 | 29 | require_keys_eq!(ctx.accounts.pay_token_mint.key(), PAY_TOKEN_MINT_ADDRESS, GigContractError::PayTokenMintError); 30 | 31 | // Check if the contract is pending which means one of two parties approved. 32 | // powi(10.0, 6) for USDC, powi(10.0, 8) for BPT for test 33 | require!(dispute == (0.5 * f64::powi(10.0, 6)).round() as u64 , GigContractError::InvalidDisputeAmount); 34 | 35 | let contract = &mut ctx.accounts.contract; 36 | let current_timestamp = Clock::get()?.unix_timestamp as u32; 37 | let token_program = &ctx.accounts.token_program; 38 | let authority = &ctx.accounts.buyer; 39 | let source = &ctx.accounts.buyer_ata; 40 | let destination = &ctx.accounts.contract_ata; 41 | 42 | contract.contract_id = contract_id; 43 | contract.buyer = ctx.accounts.buyer.key(); 44 | contract.seller = ctx.accounts.seller.key(); 45 | contract.start_time = current_timestamp; 46 | contract.hourly_rate = hourly_rate; 47 | contract.weekly_hours_limit = weekly_hours_limit; 48 | contract.dispute = dispute; 49 | contract.deadline = deadline; 50 | contract.status = HourlyContractStatus::Created; 51 | 52 | contract.buyer_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 53 | contract.seller_referral = anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3"); 54 | 55 | if let Some(buyer_referral) = &ctx.accounts.buyer_referral { 56 | msg!("buyer_referral provided: {}", buyer_referral.key()); 57 | contract.buyer_referral = buyer_referral.key(); 58 | } 59 | 60 | // Transfer paytoken(dispute) to the contract account 61 | token::transfer( 62 | CpiContext::new( 63 | token_program.to_account_info(), 64 | SplTransfer { 65 | from: source.to_account_info().clone(), 66 | to: destination.to_account_info().clone(), 67 | authority: authority.to_account_info().clone(), 68 | }, 69 | ), 70 | dispute, 71 | )?; 72 | 73 | msg!("New hourly contract created successfully on buyer side!"); 74 | Ok(()) 75 | } 76 | 77 | #[derive(Accounts)] 78 | #[instruction(contract_id: String)] 79 | pub struct StartHourlyContractOnBuyerContext<'info> { 80 | #[account(mut)] 81 | pub buyer: Signer<'info>, 82 | 83 | #[account( 84 | init, 85 | seeds = [ 86 | CONTRACT_SEED.as_bytes(), 87 | &contract_id.as_bytes() 88 | ], 89 | payer = buyer, 90 | bump, 91 | space = size_of::() + 8, 92 | )] 93 | pub contract: Account<'info, HourlyContract>, 94 | 95 | pub seller: SystemAccount<'info>, 96 | 97 | // Referral is optional 98 | pub buyer_referral: Option>, 99 | 100 | pub pay_token_mint: Account<'info, Mint>, 101 | 102 | #[account( 103 | mut, 104 | associated_token::mint = pay_token_mint, 105 | associated_token::authority = buyer, 106 | )] 107 | pub buyer_ata: Account<'info, TokenAccount>, 108 | 109 | #[account( 110 | init_if_needed, 111 | payer = buyer, 112 | associated_token::mint = pay_token_mint, 113 | associated_token::authority = contract, 114 | )] 115 | pub contract_ata: Account<'info, TokenAccount>, 116 | 117 | pub token_program: Program<'info, Token>, 118 | pub associated_token_program: Program<'info, AssociatedToken>, 119 | pub system_program: Program<'info, System>, 120 | pub rent: Sysvar<'info, Rent> 121 | } 122 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/admin_withdraw_funds.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_lang::solana_program::{system_instruction, program::invoke}; 3 | use anchor_spl::{ 4 | associated_token::AssociatedToken, 5 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 6 | }; 7 | use std::mem::size_of; 8 | 9 | use crate::state::contract::*; 10 | use crate::state::job_contract::*; 11 | use crate::constants::{ 12 | CONTRACT_SEED, 13 | PAY_TOKEN_MINT_ADDRESS, 14 | ADMIN_ADDRESS 15 | }; 16 | use crate::errors::{ 17 | GigContractError 18 | }; 19 | 20 | 21 | pub fn admin_withdraw_funds( 22 | ctx: Context, 23 | amount: u64, 24 | withdrawer_address: Pubkey, 25 | contract_type: u8 26 | ) -> Result<()> { 27 | msg!("Admin sent request to withdraw funds from the program account!"); 28 | 29 | // Check if the withdrawer is admin. 30 | require!(withdrawer_address == ADMIN_ADDRESS, GigContractError::InvalidAdmin); 31 | 32 | let program_balance; 33 | let transfer_instruction; 34 | 35 | match contract_type { 36 | 0 => { 37 | // Admin can withdraw from the standard contract account 38 | msg!("Processing withdrawal for Gig contract."); 39 | program_balance = **ctx.accounts.contract.try_borrow_lamports()?; 40 | if program_balance < amount { 41 | return Err(GigContractError::InsufficientFunds.into()); 42 | } 43 | 44 | // Create transfer instruction for standard contract account 45 | transfer_instruction = system_instruction::transfer(ctx.accounts.contract.key, withdrawer_address, amount); 46 | invoke( 47 | &transfer_instruction, 48 | &[ctx.accounts.contract.clone(), withdrawer_address.clone()], 49 | )?; 50 | } 51 | 1 => { 52 | // Admin can withdraw from the job contract account 53 | msg!("Processing withdrawal for job contract."); 54 | program_balance = **ctx.accounts.job_contract.try_borrow_lamports()?; 55 | if program_balance < amount { 56 | return Err(GigContractError::InsufficientFunds.into()); 57 | } 58 | 59 | // Create transfer instruction for job contract account 60 | transfer_instruction = system_instruction::transfer(ctx.accounts.job_contract.key, withdrawer_address, amount); 61 | invoke( 62 | &transfer_instruction, 63 | &[ctx.accounts.job_contract.clone(), withdrawer_address.clone()], 64 | )?; 65 | } 66 | _ => return Err(GigContractError::InvalidContractType.into()), // Handle invalid type 67 | } 68 | 69 | // // Invoke the transfer instruction 70 | // invoke( 71 | // &transfer_instruction, 72 | // &[ctx.accounts.contract_account.clone(), ctx.accounts.recipient.clone()], 73 | // )?; 74 | 75 | // let contract = &mut ctx.accounts.contract; 76 | 77 | 78 | // let token_program = &ctx.accounts.token_program; 79 | // let authority = &ctx.accounts.buyer; 80 | // let source = &ctx.accounts.buyer_ata; 81 | // let destination = &ctx.accounts.contract_ata; 82 | 83 | // contract.status = ContractStatus::Accepted; 84 | 85 | // if let Some(buyer_referral) = &ctx.accounts.buyer_referral { 86 | // msg!("buyer_referral provided: {}", buyer_referral.key()); 87 | // contract.buyer_referral = buyer_referral.key(); 88 | // } 89 | 90 | // // Transfer paytoken(amount + dispute) to the contract account 91 | // token::transfer( 92 | // CpiContext::new( 93 | // token_program.to_account_info(), 94 | // SplTransfer { 95 | // from: source.to_account_info().clone(), 96 | // to: destination.to_account_info().clone(), 97 | // authority: authority.to_account_info().clone(), 98 | // }, 99 | // ), 100 | // (contract.amount + contract.dispute).try_into().unwrap(), 101 | // )?; 102 | 103 | msg!("Admin Withdrew successfully!"); 104 | Ok(()) 105 | } 106 | 107 | #[derive(Accounts)] 108 | #[instruction(contract_id: String)] 109 | pub struct AdminWithdrawFundsContext<'info> { 110 | // #[account(mut)] 111 | // pub contract: AccountInfo<'info>, 112 | // #[account(mut)] 113 | // pub job_contract: AccountInfo<'info> 114 | 115 | // #[account( 116 | // mut, 117 | // seeds = [ 118 | // CONTRACT_SEED.as_bytes(), 119 | // &contract_id.as_bytes() 120 | // ], 121 | // bump, 122 | // )] 123 | #[account(mut)] 124 | pub contract: Account<'info, Contract>, 125 | #[account(mut)] 126 | pub job_contract: Account<'info, JobContract> 127 | 128 | // pub system_program: Program<'info, System>, 129 | } 130 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/job_listing_with_feature_employer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use anchor_spl::{ 4 | associated_token::AssociatedToken, 5 | token::{self, Mint, Token, TokenAccount, Transfer as SplTransfer}, 6 | }; 7 | use std::mem::size_of; 8 | 9 | use crate::state::job_contract::*; 10 | use crate::constants::{ 11 | CONTRACT_SEED, 12 | PAY_TOKEN_MINT_ADDRESS, 13 | EMPLOYER_REFERRAL, 14 | }; 15 | use crate::errors::{ 16 | GigContractError, 17 | }; 18 | 19 | pub fn job_listing_with_feature_employer( 20 | ctx: Context, 21 | contract_id: String, 22 | featured_day: u8, 23 | ) -> Result<()> { 24 | msg!("Listing Job with featured fee on employer side!"); 25 | 26 | let job_contract = &mut ctx.accounts.job_contract; 27 | 28 | // Define the fees based on featured_day 29 | let listing_fee : u64 = match featured_day { 30 | 1 => 21_000_000, // 24 hours 31 | 3 => 36_000_000, // 3 days 32 | 7 => 71_000_000, // 7 days 33 | 14 => 100_000_000, // 14 days 34 | 30 => 150_000_000,// 30 days 35 | _ => return Err(GigContractError::InvalidFeaturedDay.into()), // Handle invalid day 36 | }; 37 | 38 | let dispute_fee = 1_000_000; // Same assumption for dispute fee 39 | 40 | // Define source and destination token accounts 41 | let employer_ata = &ctx.accounts.employer_ata; 42 | let contract_ata = &ctx.accounts.contract_ata; 43 | let authority = &ctx.accounts.employer; 44 | let token_program = &ctx.accounts.token_program; 45 | 46 | // Handle referral transfer if a referral account is provided and not the excluded address 47 | if let Some(employer_referral_ata) = &ctx.accounts.employer_referral_ata { 48 | msg!("Employer refferal provided!"); 49 | if employer_referral_ata.key() != EMPLOYER_REFERRAL { 50 | msg!("Employer referral provided: {} {}", employer_referral_ata.key(), listing_fee); 51 | job_contract.employer_referral = employer_referral_ata.key(); 52 | 53 | let referral_amount: u64 = listing_fee * 10 / 100; 54 | let contract_amount: u64 = listing_fee * 90 / 100; 55 | 56 | // Transfer referral bonus 57 | token::transfer( 58 | CpiContext::new( 59 | token_program.to_account_info(), 60 | SplTransfer { 61 | from: employer_ata.to_account_info(), 62 | to: employer_referral_ata.to_account_info(), 63 | authority: authority.to_account_info(), 64 | } 65 | ), 66 | referral_amount 67 | )?; 68 | 69 | msg!("confirmed first send"); 70 | 71 | // Transfer the remaining amount to the contract 72 | token::transfer( 73 | CpiContext::new( 74 | token_program.to_account_info(), 75 | SplTransfer { 76 | from: employer_ata.to_account_info(), 77 | to: contract_ata.to_account_info(), 78 | authority: authority.to_account_info(), 79 | }, 80 | ), 81 | contract_amount 82 | )?; 83 | 84 | msg!("confirmed second send"); 85 | 86 | } else { 87 | msg!("Employer referral provided, but is the excluded address. Skipping referral bonus."); 88 | 89 | // Transfer the full listing fee to the contract 90 | token::transfer( 91 | CpiContext::new( 92 | token_program.to_account_info(), 93 | SplTransfer { 94 | from: employer_ata.to_account_info(), 95 | to: contract_ata.to_account_info(), 96 | authority: authority.to_account_info(), 97 | }, 98 | ), 99 | listing_fee as u64, 100 | )?; 101 | } 102 | } else { 103 | msg!("Employer referral not provided."); 104 | 105 | // Transfer the full listing fee to the contract 106 | token::transfer( 107 | CpiContext::new( 108 | token_program.to_account_info(), 109 | SplTransfer { 110 | from: employer_ata.to_account_info(), 111 | to: contract_ata.to_account_info(), 112 | authority: authority.to_account_info(), 113 | }, 114 | ), 115 | listing_fee as u64, 116 | )?; 117 | } 118 | 119 | msg!("Transferred listing fee of {} USDC!", listing_fee / 1_000_000); 120 | 121 | // Update contract status 122 | job_contract.contract_id = contract_id; 123 | job_contract.status = JobContractStatus::Created; 124 | job_contract.featured = true; 125 | job_contract.featured_day = featured_day; 126 | msg!("Job listed successfully!"); 127 | 128 | Ok(()) 129 | } 130 | 131 | #[derive(Accounts)] 132 | #[instruction(contract_id: String)] 133 | pub struct JobListingWithFeatureEmployerContext<'info> { 134 | #[account(mut)] 135 | pub employer: Signer<'info>, 136 | #[account( 137 | init, 138 | space = JobContract::LEN + 8, 139 | payer = employer, 140 | seeds = [ 141 | CONTRACT_SEED.as_bytes(), 142 | &contract_id.as_bytes() 143 | ], 144 | bump, 145 | )] 146 | pub job_contract: Account<'info, JobContract>, 147 | #[account(mut)] 148 | pub employer_ata: Account<'info, TokenAccount>, 149 | #[account(mut)] 150 | pub contract_ata: Account<'info, TokenAccount>, 151 | // Referral is optional, but MUST be a TokenAccount if provided 152 | // #[account(mut)] 153 | // pub employer_referral: Option>, 154 | // Optional 155 | 156 | #[account( 157 | mut, 158 | // constraint = employer_referral_ata.mint == PAY_TOKEN_MINT_ADDRESS, 159 | constraint = employer_referral_ata.owner != EMPLOYER_REFERRAL, 160 | )] 161 | pub employer_referral_ata: Option>, 162 | pub token_program: Program<'info, Token>, 163 | pub associated_token_program: Program<'info, AssociatedToken>, 164 | pub system_program: Program<'info, System>, 165 | } -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/seller_approve_hourly_contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::hourly_contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | ADMIN_ADDRESS, 12 | PAY_TOKEN_MINT_ADDRESS 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn seller_approve_hourly_contract ( 20 | ctx: Context, 21 | contract_id: String, 22 | seller_satisfied: bool 23 | ) -> Result<()> { 24 | msg!("Releasing funds on seller side!"); 25 | 26 | let contract = &mut ctx.accounts.contract; 27 | 28 | require_keys_eq!(ctx.accounts.pay_token_mint.key(), PAY_TOKEN_MINT_ADDRESS, GigContractError::PayTokenMintError); 29 | 30 | // Check if the signer is a correct seller 31 | require_keys_eq!(ctx.accounts.seller.key(), contract.seller, GigContractError::InvalidSeller); 32 | 33 | // Check if the contract is Paid. 34 | require!(contract.status == HourlyContractStatus::Paid, GigContractError::HourlyContractNotPaidYet); 35 | 36 | let token_program = &ctx.accounts.token_program; 37 | let source = &ctx.accounts.contract_ata; 38 | let seller_destination = &ctx.accounts.seller_ata; 39 | let buyer_destination = &ctx.accounts.buyer_ata; 40 | let admin_destination = &ctx.accounts.admin_ata; 41 | let buyer_referral_destination = &ctx.accounts.buyer_referral_ata; 42 | let seller_referral_destination = &ctx.accounts.seller_referral_ata; 43 | 44 | contract.status = HourlyContractStatus::Active; 45 | contract.seller_approved = true; 46 | 47 | let total_balance = source.amount - 2 * contract.dispute; 48 | 49 | // To seller 50 | token::transfer( 51 | CpiContext::new_with_signer( 52 | token_program.to_account_info(), 53 | SplTransfer { 54 | from: source.to_account_info().clone(), 55 | to: seller_destination.to_account_info().clone(), 56 | authority: contract.to_account_info().clone(), 57 | }, 58 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 59 | ), 60 | ((total_balance) * 90 / 100).try_into().unwrap(), 61 | )?; 62 | 63 | let mut admin_amount: u64 = ((total_balance) * 10 / 100).try_into().unwrap(); 64 | 65 | // To buyer referral 66 | if contract.buyer_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 67 | let buyer_referral_amount: u64 = ((total_balance) * 1 / 100).try_into().unwrap(); 68 | admin_amount -= buyer_referral_amount; 69 | 70 | token::transfer( 71 | CpiContext::new_with_signer( 72 | token_program.to_account_info(), 73 | SplTransfer { 74 | from: source.to_account_info().clone(), 75 | to: buyer_referral_destination.to_account_info().clone(), 76 | authority: contract.to_account_info().clone(), 77 | }, 78 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 79 | ), 80 | buyer_referral_amount, 81 | )?; 82 | } 83 | 84 | // To seller referral 85 | if contract.seller_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 86 | let seller_referral_amount: u64 = ((total_balance) * 1 / 100).try_into().unwrap(); 87 | admin_amount -= seller_referral_amount; 88 | 89 | token::transfer( 90 | CpiContext::new_with_signer( 91 | token_program.to_account_info(), 92 | SplTransfer { 93 | from: source.to_account_info().clone(), 94 | to: seller_referral_destination.to_account_info().clone(), 95 | authority: contract.to_account_info().clone(), 96 | }, 97 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 98 | ), 99 | seller_referral_amount, 100 | )?; 101 | } 102 | 103 | // To admin 104 | token::transfer( 105 | CpiContext::new_with_signer( 106 | token_program.to_account_info(), 107 | SplTransfer { 108 | from: source.to_account_info().clone(), 109 | to: admin_destination.to_account_info().clone(), 110 | authority: contract.to_account_info().clone(), 111 | }, 112 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 113 | ), 114 | admin_amount, 115 | )?; 116 | 117 | 118 | msg!("Funds released by seller successfully!"); 119 | Ok(()) 120 | } 121 | 122 | #[derive(Accounts)] 123 | #[instruction(contract_id: String)] 124 | pub struct SellerApproveHourlyContractContext<'info> { 125 | pub pay_token_mint: Account<'info, Mint>, // Define the mint account 126 | 127 | #[account(mut)] 128 | pub seller: Signer<'info>, 129 | 130 | pub seller_referral: SystemAccount<'info>, 131 | pub buyer_referral: SystemAccount<'info>, 132 | 133 | #[account( 134 | mut, 135 | seeds = [ 136 | CONTRACT_SEED.as_bytes(), 137 | &contract_id.as_bytes() 138 | ], 139 | bump, 140 | )] 141 | pub contract: Account<'info, HourlyContract>, 142 | 143 | #[account( 144 | mut, 145 | associated_token::mint = pay_token_mint, 146 | associated_token::authority = contract.buyer, 147 | )] 148 | pub buyer_ata: Box>, 149 | 150 | #[account( 151 | mut, 152 | associated_token::mint = pay_token_mint, 153 | associated_token::authority = contract.seller, 154 | )] 155 | pub seller_ata: Box>, 156 | 157 | #[account( 158 | init_if_needed, 159 | payer = seller, 160 | associated_token::mint = pay_token_mint, 161 | associated_token::authority = seller_referral, 162 | )] 163 | pub seller_referral_ata: Box>, 164 | 165 | #[account( 166 | init_if_needed, 167 | payer = seller, 168 | associated_token::mint = pay_token_mint, 169 | associated_token::authority = buyer_referral, 170 | )] 171 | pub buyer_referral_ata: Box>, 172 | 173 | #[account( 174 | mut, 175 | associated_token::mint = pay_token_mint, 176 | associated_token::authority = ADMIN_ADDRESS, 177 | )] 178 | pub admin_ata: Box>, 179 | 180 | #[account( 181 | mut, 182 | associated_token::mint = pay_token_mint, 183 | associated_token::authority = contract, 184 | )] 185 | pub contract_ata: Box>, 186 | 187 | 188 | pub token_program: Program<'info, Token>, 189 | pub associated_token_program: Program<'info, AssociatedToken>, 190 | pub system_program: Program<'info, System>, 191 | } 192 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | 7 | use instructions::*; 8 | 9 | pub mod instructions; 10 | pub mod constants; 11 | pub mod errors; 12 | pub mod state; 13 | 14 | declare_id!("BccHg9A6gnaC9a9NWinzqE6r7491ohyFaHWjFeYFxsBV"); 15 | 16 | #[program] 17 | pub mod gig_basic_contract { 18 | use super::*; 19 | 20 | /* 21 | Buyer will start a working contract between buyer and seller 22 | by calling this function with payment amount and dispute fee. 23 | */ 24 | 25 | pub fn start_contract_on_buyer(ctx: Context, contract_id: String, amount: u64, dispute: u64, deadline: u32) -> Result<()> { 26 | instructions::start_contract_on_buyer::start_contract_on_buyer(ctx, contract_id, amount, dispute, deadline) 27 | } 28 | 29 | /* 30 | Seller will start by calling this function wiht just dispute fee. 31 | */ 32 | 33 | pub fn start_contract_on_seller(ctx: Context, contract_id: String, amount: u64, dispute: u64, deadline: u32) -> Result<()> { 34 | instructions::start_contract_on_seller::start_contract_on_seller(ctx, contract_id, amount, dispute, deadline) 35 | } 36 | 37 | /* 38 | Buyer will accept the contract after seller creates a new contract. 39 | by calling this function with payment amount and dispute fee. 40 | */ 41 | pub fn accept_contract_on_buyer(ctx: Context, contract_id: String,) -> Result<()> { 42 | instructions::accept_contract_on_buyer::accept_contract_on_buyer(ctx, contract_id) 43 | } 44 | 45 | /* 46 | Seller will activate the contract after checking all conditions that buyer set 47 | when creating the contract. 48 | */ 49 | pub fn activate_contract(ctx: Context, contract_id: String, with_dispute: bool) -> Result<()> { 50 | instructions::activate_contract::activate_contract(ctx, contract_id, with_dispute) 51 | } 52 | 53 | /* 54 | Buyer will release funds after satisfied with products seller will deliver. 55 | Here, split will be true if buyer is dissatisfied 56 | */ 57 | pub fn buyer_approve(ctx: Context, contract_id: String, split: bool) -> Result<()> { 58 | instructions::buyer_approve::buyer_approve(ctx, contract_id, split) 59 | } 60 | 61 | /* 62 | Admin will approve if there is a dispute. 63 | decision value: 0 for both ok by default, 1 for seller, 2 for buyer, 3 for split 64 | */ 65 | pub fn admin_approve(ctx: Context, contract_id: String, decision: u8) -> Result<()> { 66 | instructions::admin_approve::admin_approve(ctx, contract_id, decision) 67 | } 68 | 69 | /* 70 | Seller will approve the amount of funds to receive 71 | Here, seller_satisfied will be true if seller agree with split payment. Otherwise false 72 | */ 73 | pub fn seller_approve(ctx: Context, contract_id: String, seller_satisfied: bool) -> Result<()> { 74 | instructions::seller_approve::seller_approve(ctx, contract_id, seller_satisfied) 75 | } 76 | 77 | 78 | // Hourly gigs part // 79 | /* 80 | Buyer will start a working hourly contract between buyer and seller 81 | by calling this function with hourly rate, weekly limit and dispute fee. 82 | */ 83 | 84 | pub fn start_hourly_contract_on_buyer(ctx: Context, contract_id: String, hourly_rate: u32, weekly_hours_limit: u32, dispute: u64, deadline: u32) -> Result<()> { 85 | instructions::start_hourly_contract_on_buyer::start_hourly_contract_on_buyer(ctx, contract_id, hourly_rate, weekly_hours_limit, dispute, deadline) 86 | } 87 | 88 | /* 89 | Seller will activate the contract after checking all conditions that buyer set 90 | when creating the contract. 91 | */ 92 | pub fn activate_hourly_contract(ctx: Context, contract_id: String, with_dispute: bool) -> Result<()> { 93 | instructions::activate_hourly_contract::activate_hourly_contract(ctx, contract_id, with_dispute) 94 | } 95 | 96 | /* 97 | Freelancer will update his worked hours per week 98 | */ 99 | pub fn update_worked_hour(ctx: Context, contract_id: String, week_worked_hour: u32) -> Result<()> { 100 | instructions::update_worked_hour::update_worked_hour(ctx, contract_id, week_worked_hour) 101 | } 102 | 103 | /* 104 | Client will pay worked hours of freelancers per week 105 | */ 106 | pub fn pay_worked_hour(ctx: Context, contract_id: String, amount: u64) -> Result<()> { 107 | instructions::pay_worked_hour::pay_worked_hour(ctx, contract_id, amount) 108 | } 109 | 110 | /* 111 | Freelancer will approve to get paid 112 | */ 113 | pub fn seller_approve_hourly_contract(ctx: Context, contract_id: String, seller_satisfied: bool) -> Result<()> { 114 | instructions::seller_approve_hourly_contract::seller_approve_hourly_contract(ctx, contract_id, seller_satisfied) 115 | } 116 | 117 | /* 118 | Client will end hourly contracts 119 | */ 120 | pub fn end_hourly_contract(ctx: Context, contract_id: String) -> Result<()> { 121 | instructions::end_hourly_contract::end_hourly_contract(ctx, contract_id) 122 | } 123 | 124 | /* 125 | Client will pause hourly contracts 126 | */ 127 | pub fn pause_hourly_contract(ctx: Context, contract_id: String) -> Result<()> { 128 | instructions::pause_hourly_contract::pause_hourly_contract(ctx, contract_id) 129 | } 130 | 131 | /* 132 | Client will resume hourly contracts 133 | */ 134 | pub fn resume_hourly_contract(ctx: Context, contract_id: String) -> Result<()> { 135 | instructions::resume_hourly_contract::resume_hourly_contract(ctx, contract_id) 136 | } 137 | 138 | /* 139 | Job Listing on Employer side with $1 fee 140 | */ 141 | pub fn job_listing_with_one_fee_employer(ctx: Context, contract_id: String) -> Result<()> { 142 | instructions::job_listing_with_one_fee_employer::job_listing_with_one_fee_employer(ctx, contract_id) 143 | } 144 | 145 | /* 146 | Job Listing on Employer side with feature fee 147 | */ 148 | pub fn job_listing_with_feature_employer(ctx: Context, contract_id: String, featured_day: u8) -> Result<()> { 149 | instructions::job_listing_with_feature_employer::job_listing_with_feature_employer(ctx, contract_id, featured_day) 150 | } 151 | 152 | /* 153 | Admin will withdraw funds from the contract 154 | */ 155 | pub fn admin_withdraw_job_contract(ctx: Context, contract_id: String) -> Result<()> { 156 | instructions::admin_withdraw_job::admin_withdraw_job_contract(ctx, contract_id) 157 | } 158 | } -------------------------------------------------------------------------------- /tests/gig-basic-contract2.test: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program, Wallet } from "@coral-xyz/anchor"; 3 | import { expect, assert } from "chai"; 4 | import { 5 | getOrCreateAssociatedTokenAccount, 6 | getAssociatedTokenAddressSync, 7 | transfer, 8 | TOKEN_PROGRAM_ID, 9 | ASSOCIATED_TOKEN_PROGRAM_ID, 10 | createMint, 11 | mintTo, 12 | } from "@solana/spl-token"; 13 | import { GigBasicContract } from "../target/types/gig_basic_contract"; 14 | import { v4 as uuid } from "uuid"; 15 | 16 | describe("gig-basic-contract", () => { 17 | // Configure the client to use the local cluster. 18 | const provider = anchor.AnchorProvider.env(); 19 | anchor.setProvider(provider); 20 | const program = anchor.workspace 21 | .GigBasicContract as Program; 22 | const wallet = anchor.Wallet.local(); 23 | const signer = wallet.payer; 24 | let employer: anchor.web3.Keypair; 25 | let jobContract: anchor.web3.PublicKey; 26 | let employerAta: anchor.web3.PublicKey; 27 | let contractAta: anchor.web3.PublicKey; 28 | let employerReferralAta: anchor.web3.PublicKey; 29 | let PAY_TOKEN_MINT_ADDRESS: anchor.web3.PublicKey; 30 | let ASSOCIATED_TOKEN_PROGRAM_ID: anchor.web3.PublicKey; 31 | let contractId: string; 32 | 33 | before(async () => { 34 | console.log("------------- before -------------"); 35 | // Create employer account 36 | employer = anchor.web3.Keypair.generate(); 37 | 38 | // sol transfer 39 | const transaction = new anchor.web3.Transaction(); 40 | 41 | transaction.add( 42 | anchor.web3.SystemProgram.transfer({ 43 | fromPubkey: signer.publicKey, 44 | toPubkey: employer.publicKey, 45 | lamports: 1 * anchor.web3.LAMPORTS_PER_SOL, 46 | }) 47 | ); 48 | 49 | contractId = uuid().slice(0, 8); 50 | console.log("new contractId:", contractId); 51 | 52 | // Create job contract account 53 | [jobContract, ] = await anchor.web3.PublicKey.findProgramAddressSync( 54 | [ 55 | Buffer.from(anchor.utils.bytes.utf8.encode("gig_contract")), 56 | Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 57 | ], 58 | program.programId 59 | ); 60 | 61 | console.log("jobContract: ", jobContract.toBase58()); 62 | 63 | const txHash = await provider.connection.sendTransaction(transaction, [ 64 | signer, 65 | ]); 66 | console.log("Transaction hash: ", txHash); 67 | 68 | const txConfirm = await provider.connection.confirmTransaction(txHash); 69 | console.log("Transaction hash confirm: ", txConfirm); 70 | 71 | const balance = await provider.connection.getBalance(signer.publicKey); 72 | const employerBalance = await provider.connection.getBalance( 73 | employer.publicKey 74 | ); 75 | const jobContractBalance = await provider.connection.getBalance( 76 | jobContract 77 | ); 78 | console.log(` 79 | balance: ${balance / anchor.web3.LAMPORTS_PER_SOL}, 80 | employerBalance: ${employerBalance / anchor.web3.LAMPORTS_PER_SOL}, 81 | jobContractBalance: ${jobContractBalance / anchor.web3.LAMPORTS_PER_SOL} 82 | `); 83 | 84 | PAY_TOKEN_MINT_ADDRESS = new anchor.web3.PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU") 85 | 86 | // // Create the custom USDT-like token mint 87 | // PAY_TOKEN_MINT_ADDRESS = await createMint( 88 | // provider.connection, 89 | // employer, 90 | // employer.publicKey, 91 | // null, 92 | // 6 93 | // ); 94 | 95 | // console.log("New token mint: ", PAY_TOKEN_MINT_ADDRESS.toBase58()); 96 | 97 | // Create associated token accounts for employer and contract 98 | // Create associated token accounts for employer and contract 99 | employerAta = ( 100 | await getOrCreateAssociatedTokenAccount( 101 | provider.connection, 102 | employer, 103 | PAY_TOKEN_MINT_ADDRESS, 104 | signer.publicKey 105 | ) 106 | ).address; // Access the address property 107 | 108 | 109 | console.log("employerAta: ", employerAta.toBase58()); 110 | 111 | contractAta = ( 112 | await getOrCreateAssociatedTokenAccount( 113 | provider.connection, 114 | employer, 115 | PAY_TOKEN_MINT_ADDRESS, 116 | jobContract, 117 | true, 118 | ) 119 | ).address; // Access the address property 120 | 121 | console.log("contractAta: ", contractAta.toBase58()); 122 | employerReferralAta = ( 123 | await getOrCreateAssociatedTokenAccount( 124 | provider.connection, 125 | employer, 126 | PAY_TOKEN_MINT_ADDRESS, 127 | signer.publicKey 128 | ) 129 | ).address; 130 | console.log("employerReferralAta: ", employerReferralAta.toBase58()); 131 | 132 | // await mintTo( 133 | // provider.connection, 134 | // employer, 135 | // PAY_TOKEN_MINT_ADDRESS, 136 | // employerAta, 137 | // employer.publicKey, 138 | // 1_000_000_000 139 | // ); 140 | 141 | const tokenBalance = await provider.connection.getTokenAccountBalance( 142 | employerAta 143 | ); 144 | console.log("Token balance: ", tokenBalance); 145 | 146 | // Initialize the job contract and associated token accounts as needed 147 | // (You would need to implement this part based on your contract's logic) 148 | }); 149 | 150 | // it("should create a job listing", async () => { 151 | // console.log("------------- creating a job listing -------------"); 152 | // }); 153 | 154 | // it("Should list a job with a $1 fee on the employer side", async () => { 155 | // // Call the job_listing_with_one_fee_employer function 156 | // const tx = await program.methods 157 | // .jobListingWithOneFeeEmployer(contractId) 158 | // .accounts({ 159 | // employer: signer.publicKey, 160 | // jobContract: jobContract, 161 | // employerAta: employerAta, 162 | // contractAta: contractAta, 163 | // }) 164 | // .signers([signer]) 165 | // .rpc(); 166 | 167 | // console.log("Transaction signature", tx); 168 | 169 | // // Add assertions to check the state after the transaction 170 | // // For example, check if the job contract status is updated 171 | // const updatedJobContract = await program.account.jobContract.fetch( 172 | // jobContract 173 | // ); 174 | 175 | 176 | // const statusKeys = Object.keys(updatedJobContract.status); 177 | // console.log("statusKeys: ", statusKeys); 178 | 179 | // const tokenBalance = await provider.connection.getTokenAccountBalance( 180 | // employerAta 181 | // ); 182 | // console.log("Token balance: ", tokenBalance); 183 | 184 | // assert.equal( 185 | // statusKeys[0], 186 | // "created", 187 | // "Job contract status should be 'Listed'" 188 | // ); 189 | // }); 190 | 191 | it("Here Should list a job with a featured fee on the employer side", async () => { 192 | console.log("------------- creating a featured job listing -------------"); 193 | 194 | const featuredDay = 3; // Example: listing for 3 days 195 | const expectedFee = 36_000_000; // Expected fee for 3 days 196 | 197 | 198 | // Fetch the initial token balance of the employer 199 | const initialEmployerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 200 | console.log("Initial Employer Token Balance: ", initialEmployerTokenBalance.value.amount); 201 | 202 | // Fetch the initial token balance of the contract 203 | const initialContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 204 | console.log("Initial Contract Token Balance: ", initialContractTokenBalance.value.amount); 205 | 206 | // Call the job_listing_with_feature_employer function 207 | const tx = await program.methods 208 | .jobListingWithFeatureEmployer(contractId, featuredDay) 209 | .accounts({ 210 | employer: employer.publicKey, 211 | jobContract: jobContract, 212 | employerAta: employerAta, 213 | contractAta: contractAta, 214 | employerReferral: employerReferralAta, 215 | tokenProgram: TOKEN_PROGRAM_ID, 216 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 217 | systemProgram: anchor.web3.SystemProgram.programId, 218 | }) 219 | .signers([employer]) 220 | .rpc(); 221 | 222 | console.log("Transaction signature", tx); 223 | 224 | // Fetch the updated job contract 225 | const updatedJobContract = await program.account.jobContract.fetch(jobContract); 226 | console.log("updatedJobContract: ", updatedJobContract); 227 | 228 | 229 | 230 | // Check if the employer's token balance is reduced by the listing fee 231 | const employerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 232 | assert.equal( 233 | employerTokenBalance.value.amount, 234 | (1_000_000_000 - expectedFee).toString(), 235 | "Employer's token balance should reflect the listing fee deduction" 236 | ); 237 | 238 | // Fetch the updated token balance of the employer after job posting 239 | const updatedEmployerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 240 | console.log("Updated Employer Token Balance: ", updatedEmployerTokenBalance.value.amount); 241 | 242 | // Fetch the updated token balance of the contract after job posting 243 | const updatedContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 244 | console.log("Updated Contract Token Balance: ", updatedContractTokenBalance.value.amount); 245 | 246 | 247 | console.log("Featured job listing created successfully!"); 248 | }); 249 | }); 250 | -------------------------------------------------------------------------------- /tests/gig-basic-contract.test.test: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program, Wallet } from "@coral-xyz/anchor"; 3 | import { expect, assert } from "chai"; 4 | import { 5 | getOrCreateAssociatedTokenAccount, 6 | getAssociatedTokenAddressSync, 7 | transfer, 8 | TOKEN_PROGRAM_ID, 9 | ASSOCIATED_TOKEN_PROGRAM_ID, 10 | createMint, 11 | mintTo, 12 | } from "@solana/spl-token"; 13 | import { GigBasicContract } from "../target/types/gig_basic_contract"; 14 | import { v4 as uuid } from "uuid"; 15 | 16 | describe("gig-basic-contract", () => { 17 | // Configure the client to use the local cluster. 18 | const provider = anchor.AnchorProvider.env(); 19 | anchor.setProvider(provider); 20 | const program = anchor.workspace 21 | .GigBasicContract as Program; 22 | const wallet = anchor.Wallet.local(); 23 | const signer = wallet.payer; 24 | let employer: anchor.web3.Keypair; 25 | let gigContract: anchor.web3.PublicKey; 26 | let jobContract: anchor.web3.PublicKey; 27 | let employerAta: anchor.web3.PublicKey; 28 | let contractAta: anchor.web3.PublicKey; 29 | let adminAta: anchor.web3.PublicKey; 30 | let PAY_TOKEN_MINT_ADDRESS: anchor.web3.PublicKey; 31 | let ASSOCIATED_TOKEN_PROGRAM_ID: anchor.web3.PublicKey; 32 | let contractId: string; 33 | 34 | before(async () => { 35 | console.log("------------- before -------------"); 36 | // Create employer account 37 | employer = anchor.web3.Keypair.generate(); 38 | 39 | // sol transfer 40 | const transaction = new anchor.web3.Transaction(); 41 | 42 | transaction.add( 43 | anchor.web3.SystemProgram.transfer({ 44 | fromPubkey: signer.publicKey, 45 | toPubkey: employer.publicKey, 46 | lamports: 1 * anchor.web3.LAMPORTS_PER_SOL, 47 | }) 48 | ); 49 | 50 | contractId = uuid().slice(0, 8); 51 | console.log("new contractId:", contractId); 52 | 53 | //create gig contract account 54 | // [gigContract, ] = await anchor.web3.PublicKey.findProgramAddressSync( 55 | // [ 56 | // Buffer.from(anchor.utils.bytes.utf8.encode("gig_contract")), 57 | 58 | // ] 59 | // ) 60 | // Create job contract account 61 | [jobContract, ] = await anchor.web3.PublicKey.findProgramAddressSync( 62 | [ 63 | Buffer.from(anchor.utils.bytes.utf8.encode("gig_contract")), 64 | Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 65 | ], 66 | program.programId 67 | ); 68 | 69 | console.log("jobContract: ", jobContract.toBase58()); 70 | 71 | const txHash = await provider.connection.sendTransaction(transaction, [ 72 | signer, 73 | ]); 74 | console.log("Transaction hash: ", txHash); 75 | 76 | const txConfirm = await provider.connection.confirmTransaction(txHash); 77 | console.log("Transaction hash confirm: ", txConfirm); 78 | 79 | const balance = await provider.connection.getBalance(signer.publicKey); 80 | const employerBalance = await provider.connection.getBalance( 81 | employer.publicKey 82 | ); 83 | const jobContractBalance = await provider.connection.getBalance( 84 | jobContract 85 | ); 86 | console.log(` 87 | balance: ${balance / anchor.web3.LAMPORTS_PER_SOL}, 88 | employerBalance: ${employerBalance / anchor.web3.LAMPORTS_PER_SOL}, 89 | jobContractBalance: ${jobContractBalance / anchor.web3.LAMPORTS_PER_SOL} 90 | `); 91 | 92 | PAY_TOKEN_MINT_ADDRESS = new anchor.web3.PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU") 93 | 94 | // // Create the custom USDT-like token mint 95 | // PAY_TOKEN_MINT_ADDRESS = await createMint( 96 | // provider.connection, 97 | // employer, 98 | // employer.publicKey, 99 | // null, 100 | // 6 101 | // ); 102 | 103 | // console.log("New token mint: ", PAY_TOKEN_MINT_ADDRESS.toBase58()); 104 | 105 | // Create associated token accounts for employer and contract 106 | // Create associated token accounts for employer and contract 107 | employerAta = ( 108 | await getOrCreateAssociatedTokenAccount( 109 | provider.connection, 110 | employer, 111 | PAY_TOKEN_MINT_ADDRESS, 112 | signer.publicKey 113 | ) 114 | ).address; // Access the address property 115 | 116 | // adminAta = ( 117 | // await getOrCreateAssociatedTokenAccount( 118 | // provider.connection, 119 | // new anchor.web3.Keypair("384WTsaMenUoXE89jjJvLSWcG7hUeqN8pcxwV1KNVuHd"), 120 | // PAY_TOKEN_MINT_ADDRESS, 121 | // signer.publicKey 122 | // ) 123 | // ).address; 124 | 125 | 126 | console.log("employerAta: ", employerAta.toBase58()); 127 | 128 | contractAta = ( 129 | await getOrCreateAssociatedTokenAccount( 130 | provider.connection, 131 | employer, 132 | PAY_TOKEN_MINT_ADDRESS, 133 | jobContract, 134 | true, 135 | ) 136 | ).address; // Access the address property 137 | 138 | console.log("contractAta: ", contractAta.toBase58()); 139 | 140 | // await mintTo( 141 | // provider.connection, 142 | // employer, 143 | // PAY_TOKEN_MINT_ADDRESS, 144 | // employerAta, 145 | // employer.publicKey, 146 | // 1_000_000_000 147 | // ); 148 | 149 | const tokenBalance = await provider.connection.getTokenAccountBalance( 150 | employerAta 151 | ); 152 | console.log("Token balance: ", tokenBalance); 153 | 154 | // Initialize the job contract and associated token accounts as needed 155 | // (You would need to implement this part based on your contract's logic) 156 | }); 157 | 158 | // it("should create a job listing", async () => { 159 | // console.log("------------- creating a job listing -------------"); 160 | // }); 161 | 162 | // it("Should list a job with a $1 fee on the employer side", async () => { 163 | // // Call the job_listing_with_one_fee_employer function 164 | // const tx = await program.methods 165 | // .jobListingWithOneFeeEmployer(contractId) 166 | // .accounts({ 167 | // employer: signer.publicKey, 168 | // jobContract: jobContract, 169 | // employerAta: employerAta, 170 | // contractAta: contractAta, 171 | // }) 172 | // .signers([signer]) 173 | // .rpc(); 174 | 175 | // console.log("Transaction signature", tx); 176 | 177 | // // Add assertions to check the state after the transaction 178 | // // For example, check if the job contract status is updated 179 | // const updatedJobContract = await program.account.jobContract.fetch( 180 | // jobContract 181 | // ); 182 | 183 | 184 | // const statusKeys = Object.keys(updatedJobContract.status); 185 | // console.log("statusKeys: ", statusKeys); 186 | 187 | // const tokenBalance = await provider.connection.getTokenAccountBalance( 188 | // employerAta 189 | // ); 190 | // console.log("Token balance: ", tokenBalance); 191 | 192 | // assert.equal( 193 | // statusKeys[0], 194 | // "created", 195 | // "Job contract status should be 'Listed'" 196 | // ); 197 | // }); 198 | 199 | // it("Should list a job with a featured fee on the employer side", async () => { 200 | // console.log("------------- creating a featured job listing -------------"); 201 | 202 | // const featuredDay = 3; // Example: listing for 3 days 203 | // const expectedFee = 36_000_000; // Expected fee for 3 days 204 | 205 | 206 | // // Fetch the initial token balance of the employer 207 | // const initialEmployerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 208 | // console.log("Initial Employer Token Balance: ", initialEmployerTokenBalance.value.amount); 209 | 210 | // // Fetch the initial token balance of the contract 211 | // const initialContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 212 | // console.log("Initial Contract Token Balance: ", initialContractTokenBalance.value.amount); 213 | 214 | // // Call the job_listing_with_feature_employer function 215 | // const tx = await program.methods 216 | // .jobListingWithFeatureEmployer(contractId, featuredDay) 217 | // .accounts({ 218 | // employer: employer.publicKey, 219 | // jobContract: jobContract, 220 | // employerAta: employerAta, 221 | // contractAta: contractAta, 222 | // tokenProgram: TOKEN_PROGRAM_ID, 223 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 224 | // systemProgram: anchor.web3.SystemProgram.programId, 225 | // }) 226 | // .signers([employer]) 227 | // .rpc(); 228 | 229 | // console.log("Transaction signature", tx); 230 | 231 | // // Fetch the updated job contract 232 | // const updatedJobContract = await program.account.jobContract.fetch(jobContract); 233 | // console.log("updatedJobContract: ", updatedJobContract); 234 | 235 | 236 | 237 | // // Check if the employer's token balance is reduced by the listing fee 238 | // const employerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 239 | // assert.equal( 240 | // employerTokenBalance.value.amount, 241 | // (1_000_000_000 - expectedFee).toString(), 242 | // "Employer's token balance should reflect the listing fee deduction" 243 | // ); 244 | 245 | // // Fetch the updated token balance of the employer after job posting 246 | // const updatedEmployerTokenBalance = await provider.connection.getTokenAccountBalance(employerAta); 247 | // console.log("Updated Employer Token Balance: ", updatedEmployerTokenBalance.value.amount); 248 | 249 | // // Fetch the updated token balance of the contract after job posting 250 | // const updatedContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 251 | // console.log("Updated Contract Token Balance: ", updatedContractTokenBalance.value.amount); 252 | 253 | 254 | // console.log("Featured job listing created successfully!"); 255 | // }); 256 | 257 | it("Should withdraw some amount from the program account on admin side", async () => { 258 | console.log("------------- withdrawing some funds from the program account -------------"); 259 | 260 | const amount = 1_000_000; 261 | const withdrawer_address = "384WTsaMenUoXE89jjJvLSWcG7hUeqN8pcxwV1KNVuHd"; 262 | const contract_type = 0; 263 | 264 | // Fetch the initial token balance of the admin 265 | // const initialAdminTokenBalance = await provider.connection.getTokenAccountBalance(adminAta); 266 | // console.log("Initial Employer Token Balance: ", initialEmployerTokenBalance.value.amount); 267 | 268 | // Fetch the initial token balance of the contract 269 | const initialGigContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 270 | console.log("Initial Gig Contract Token Balance: ", initialGigContractTokenBalance.value.amount); 271 | 272 | const tx = await program.rpc.adminWithdrawFunds( 273 | new anchor.BN(amount), 274 | withdrawer_address, 275 | 0, // contract_type 0 for gig contract 276 | { 277 | accounts: { 278 | contract: contractAta, 279 | jobContractAccount: jobContract 280 | }, 281 | } 282 | ); 283 | 284 | console.log("Transaction signature", tx); 285 | 286 | // Fetch the updated job contract 287 | // Fetch updated balances after withdrawal 288 | const updatedContractTokenBalance = await provider.connection.getTokenAccountBalance(contractAta); 289 | 290 | console.log("Updated Gig Contract Token Balance: ", updatedContractTokenBalance.value.amount); 291 | 292 | // Check if the balance has decreased by the withdrawn amount 293 | assert.equal( 294 | parseInt(updatedContractTokenBalance.value.amount), 295 | parseInt(initialGigContractTokenBalance.value.amount) - amount, 296 | "Gig Contract Token Balance should reflect the withdrawal" 297 | ); 298 | 299 | console.log("Withdrawal test completed successfully!"); 300 | }); 301 | }); 302 | -------------------------------------------------------------------------------- /tests/admin-withdraw.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program, Wallet } from "@coral-xyz/anchor"; 3 | import { expect, assert } from "chai"; 4 | import { 5 | getOrCreateAssociatedTokenAccount, 6 | getAssociatedTokenAddressSync, 7 | transfer, 8 | TOKEN_PROGRAM_ID, 9 | ASSOCIATED_TOKEN_PROGRAM_ID, 10 | createMint, 11 | mintTo, 12 | } from "@solana/spl-token"; 13 | import { GigBasicContract } from "../target/types/gig_basic_contract"; 14 | import { v4 as uuid } from "uuid"; 15 | import { Keypair } from "@solana/web3.js"; 16 | 17 | describe("Admin Withdraw", () => { 18 | const provider = anchor.AnchorProvider.env(); 19 | anchor.setProvider(provider); 20 | const program = anchor.workspace 21 | .GigBasicContract as Program; 22 | const wallet = anchor.Wallet.local(); 23 | const signer = wallet.payer; 24 | let employer: anchor.web3.Keypair; 25 | let jobContract: anchor.web3.PublicKey; 26 | let employerAta: anchor.web3.PublicKey; 27 | let contractAta: anchor.web3.PublicKey; 28 | let employerReferral: anchor.web3.PublicKey; 29 | let employerReferralAta: anchor.web3.PublicKey; 30 | let PAY_TOKEN_MINT_ADDRESS: anchor.web3.Keypair; 31 | let ASSOCIATED_TOKEN_PROGRAM_ID: anchor.web3.PublicKey; 32 | let contractId: string; 33 | 34 | before(async () => { 35 | console.log("------------- before -------------"); 36 | // Create employer account 37 | employer = anchor.web3.Keypair.generate(); 38 | employerReferral = new anchor.web3.PublicKey("FYNfBvTVTNrYGfjmHfETkEWb5xFEcDxKYqBgsGZxtJJD"); 39 | // sol transfer 40 | const transaction = new anchor.web3.Transaction(); 41 | 42 | transaction.add( 43 | anchor.web3.SystemProgram.transfer({ 44 | fromPubkey: signer.publicKey, 45 | toPubkey: employer.publicKey, 46 | lamports: 1 * anchor.web3.LAMPORTS_PER_SOL, 47 | }) 48 | ); 49 | 50 | contractId = uuid().slice(0, 8); 51 | console.log("new contractId:", contractId); 52 | 53 | // Create job contract account 54 | [jobContract] = await anchor.web3.PublicKey.findProgramAddressSync( 55 | [ 56 | Buffer.from(anchor.utils.bytes.utf8.encode("gig_contract")), 57 | Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 58 | ], 59 | program.programId 60 | ); 61 | 62 | console.log("jobContract: ", jobContract.toBase58()); 63 | 64 | const txHash = await provider.connection.sendTransaction(transaction, [ 65 | signer, 66 | ]); 67 | console.log("Transaction hash: ", txHash); 68 | 69 | const txConfirm = await provider.connection.confirmTransaction(txHash); 70 | console.log("Transaction hash confirm: ", txConfirm); 71 | 72 | const balance = await provider.connection.getBalance(signer.publicKey); 73 | const employerBalance = await provider.connection.getBalance( 74 | employer.publicKey 75 | ); 76 | const jobContractBalance = await provider.connection.getBalance( 77 | jobContract 78 | ); 79 | console.log(` 80 | balance: ${balance / anchor.web3.LAMPORTS_PER_SOL}, 81 | employerBalance: ${employerBalance / anchor.web3.LAMPORTS_PER_SOL}, 82 | jobContractBalance: ${ 83 | jobContractBalance / anchor.web3.LAMPORTS_PER_SOL 84 | } 85 | `); 86 | 87 | PAY_TOKEN_MINT_ADDRESS = Keypair.generate(); 88 | 89 | // Create the custom USDT-like token mint 90 | await createMint( 91 | provider.connection, 92 | employer, 93 | employer.publicKey, 94 | null, 95 | 6, 96 | PAY_TOKEN_MINT_ADDRESS 97 | ); 98 | 99 | console.log("New token mint: ", PAY_TOKEN_MINT_ADDRESS.publicKey.toBase58()); 100 | 101 | // Create associated token accounts for employer and contract 102 | // Create associated token accounts for employer and contract 103 | employerAta = ( 104 | await getOrCreateAssociatedTokenAccount( 105 | provider.connection, 106 | employer, 107 | PAY_TOKEN_MINT_ADDRESS.publicKey, 108 | employer.publicKey 109 | ) 110 | ).address; // Access the address property 111 | 112 | console.log("employerAta: ", employerAta.toBase58()); 113 | 114 | employerReferralAta = ( 115 | await getOrCreateAssociatedTokenAccount( 116 | provider.connection, 117 | employer, 118 | PAY_TOKEN_MINT_ADDRESS.publicKey, 119 | employerReferral 120 | ) 121 | ).address; // Access the address property 122 | 123 | console.log("employerReferralAta: ", employerReferralAta.toBase58()); 124 | 125 | contractAta = ( 126 | await getOrCreateAssociatedTokenAccount( 127 | provider.connection, 128 | employer, 129 | PAY_TOKEN_MINT_ADDRESS.publicKey, 130 | jobContract, 131 | true 132 | ) 133 | ).address; // Access the address property 134 | 135 | console.log("contractAta: ", contractAta.toBase58()); 136 | 137 | await mintTo( 138 | provider.connection, 139 | employer, 140 | PAY_TOKEN_MINT_ADDRESS.publicKey, 141 | employerAta, 142 | employer.publicKey, 143 | 1_000_000_000 144 | ); 145 | 146 | const tokenBalance = await provider.connection.getTokenAccountBalance( 147 | employerAta 148 | ); 149 | console.log("Token balance: ", tokenBalance); 150 | 151 | // Initialize the job contract and associated token accounts as needed 152 | // (You would need to implement this part based on your contract's logic) 153 | }); 154 | 155 | // it("should create a job listing", async () => { 156 | // console.log("------------- creating a job listing -------------"); 157 | // }); 158 | 159 | // it("Should list a job with a $1 fee on the employer side", async () => { 160 | // // Call the job_listing_with_one_fee_employer function 161 | // const tx = await program.methods 162 | // .jobListingWithOneFeeEmployer(contractId) 163 | // .accounts({ 164 | // employer: employer.publicKey, 165 | // jobContract: jobContract, 166 | // employerAta: employerAta, 167 | // contractAta: contractAta, 168 | // employerReferralAta: "H91b4jtXRmqoD5JY4oEJRCiggzZnbmmkox5itHeYM9cE", 169 | // tokenProgram: TOKEN_PROGRAM_ID, 170 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 171 | // systemProgram: anchor.web3.SystemProgram.programId, 172 | // }) 173 | // .signers([employer]) 174 | // .rpc(); 175 | 176 | // console.log("Transaction signature", tx); 177 | 178 | // // Add assertions to check the state after the transaction 179 | // // For example, check if the job contract status is updated 180 | // const updatedJobContract = await program.account.jobContract.fetch( 181 | // jobContract 182 | // ); 183 | 184 | // const statusKeys = Object.keys(updatedJobContract.status); 185 | // console.log("statusKeys: ", statusKeys); 186 | 187 | // const tokenBalance = await provider.connection.getTokenAccountBalance( 188 | // employerAta 189 | // ); 190 | // console.log("Token balance: ", tokenBalance); 191 | // const employerReferralTokenBalance = await provider.connection.getTokenAccountBalance( 192 | // employerReferralAta 193 | // ); 194 | // console.log("EmployerReferralTokenBalance ===> : ", employerReferralTokenBalance); 195 | // assert.equal( 196 | // statusKeys[0], 197 | // "created", 198 | // "Job contract status should be 'Listed'" 199 | // ); 200 | // }); 201 | 202 | // it("have to withdraw the admin fee", async () => { 203 | // const balance = await provider.connection.getTokenAccountBalance(contractAta); 204 | // console.log("Contract token balance: ", contractAta.toBase58(), "\n", balance); 205 | 206 | 207 | // const withdrawATA = await getOrCreateAssociatedTokenAccount( 208 | // provider.connection, 209 | // signer, 210 | // PAY_TOKEN_MINT_ADDRESS, 211 | // signer.publicKey 212 | // ); 213 | 214 | // const withdrawAddressBalance = await provider.connection.getTokenAccountBalance(withdrawATA.address); 215 | // console.log("Withdraw address balance: ", withdrawATA.address.toBase58(), "\n", contractId, withdrawAddressBalance); 216 | 217 | 218 | 219 | 220 | 221 | // const tx = await program.methods 222 | // .adminWithdrawJobContract(contractId) 223 | // .accounts({ 224 | // contract: jobContract, 225 | // contractAta: contractAta, 226 | // admin: signer.publicKey, 227 | // tokenProgram: TOKEN_PROGRAM_ID, 228 | // withdrawAddress: withdrawATA.address, 229 | // payTokenMint: PAY_TOKEN_MINT_ADDRESS, 230 | // }) 231 | // .signers([signer]) 232 | // .rpc(); 233 | 234 | // console.log("Transaction signature", tx); 235 | 236 | // const postBalance = await provider.connection.getTokenAccountBalance(contractAta); 237 | // console.log("Contract token balance: ", contractAta.toBase58(), "\n", postBalance.value.uiAmount); 238 | 239 | 240 | // const withdrawAddressPostBalance = await provider.connection.getTokenAccountBalance(withdrawATA.address); 241 | // console.log("Withdraw address balance: ", withdrawATA.address.toBase58(), "\n", contractId, withdrawAddressPostBalance.value.uiAmount); 242 | 243 | 244 | 245 | // }); 246 | 247 | it("Should list a job with a featured fee on the employer side", async () => { 248 | console.log("------------- creating a featured job listing -------------"); 249 | 250 | const featuredDay = 3; // Example: listing for 3 days 251 | const expectedFee = 36_000_000; // Expected fee for 3 days 252 | 253 | // Fetch the initial token balance of the employer 254 | const initialEmployerTokenBalance = 255 | await provider.connection.getTokenAccountBalance(employerAta); 256 | console.log( 257 | "Initial Employer Token Balance: ", 258 | initialEmployerTokenBalance.value.amount 259 | ); 260 | 261 | // Fetch the initial token balance of the contract 262 | const initialContractTokenBalance = 263 | await provider.connection.getTokenAccountBalance(contractAta); 264 | console.log( 265 | "Initial Contract Token Balance: ", 266 | initialContractTokenBalance.value.amount 267 | ); 268 | 269 | // Call the job_listing_with_feature_employer function 270 | const tx = await program.methods 271 | .jobListingWithFeatureEmployer(contractId, featuredDay) 272 | .accounts({ 273 | employer: employer.publicKey, 274 | jobContract: jobContract, 275 | employerAta: employerAta, 276 | contractAta: contractAta, 277 | employerReferralAta: null, 278 | tokenProgram: TOKEN_PROGRAM_ID, 279 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 280 | systemProgram: anchor.web3.SystemProgram.programId, 281 | }) 282 | .signers([employer]) 283 | .rpc(); 284 | 285 | console.log("Transaction signature", tx); 286 | 287 | // Fetch the updated job contract 288 | const updatedJobContract = await program.account.jobContract.fetch( 289 | jobContract 290 | ); 291 | console.log("updatedJobContract: ", updatedJobContract); 292 | 293 | // Check if the employer's token balance is reduced by the listing fee 294 | const employerTokenBalance = 295 | await provider.connection.getTokenAccountBalance(employerAta); 296 | assert.equal( 297 | employerTokenBalance.value.amount, 298 | (1_000_000_000 - expectedFee).toString(), 299 | "Employer's token balance should reflect the listing fee deduction" 300 | ); 301 | 302 | // Fetch the updated token balance of the employer after job posting 303 | const updatedEmployerTokenBalance = 304 | await provider.connection.getTokenAccountBalance(employerAta); 305 | console.log( 306 | "Updated Employer Token Balance: ", 307 | updatedEmployerTokenBalance.value.amount 308 | ); 309 | 310 | // Fetch the updated token balance of the contract after job posting 311 | const updatedContractTokenBalance = 312 | await provider.connection.getTokenAccountBalance(contractAta); 313 | console.log( 314 | "Updated Contract Token Balance: ", 315 | updatedContractTokenBalance.value.amount 316 | ); 317 | 318 | console.log("Featured job listing created successfully!"); 319 | }); 320 | }); 321 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/seller_approve.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | ADMIN_ADDRESS, 12 | PAY_TOKEN_MINT_ADDRESS 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn seller_approve( 20 | ctx: Context, 21 | contract_id: String, 22 | seller_satisfied: bool 23 | ) -> Result<()> { 24 | msg!("Releasing funds on seller side!"); 25 | 26 | let contract = &mut ctx.accounts.contract; 27 | 28 | require_keys_eq!(ctx.accounts.pay_token_mint.key(), PAY_TOKEN_MINT_ADDRESS, GigContractError::PayTokenMintError); 29 | 30 | // Check if the signer is a correct seller 31 | require_keys_eq!(ctx.accounts.seller.key(), contract.seller, GigContractError::InvalidSeller); 32 | 33 | // Check if the contract is Active or pending. 34 | require!(contract.status == ContractStatus::Active || contract.status == ContractStatus::Pending, GigContractError::CantRelease); 35 | 36 | let token_program = &ctx.accounts.token_program; 37 | let source = &ctx.accounts.contract_ata; 38 | let seller_destination = &ctx.accounts.seller_ata; 39 | let buyer_destination = &ctx.accounts.buyer_ata; 40 | let admin_destination = &ctx.accounts.admin_ata; 41 | let buyer_referral_destination = &ctx.accounts.buyer_referral_ata; 42 | let seller_referral_destination = &ctx.accounts.seller_referral_ata; 43 | 44 | contract.status = ContractStatus::Pending; 45 | contract.seller_approved = true; 46 | 47 | let total_balance = source.amount; 48 | 49 | // If both parties approve, transfer funds from the contrac to seller 50 | // dispute for both party and platform fee to admin 51 | if contract.buyer_approved == true { 52 | if contract.split == true { 53 | if seller_satisfied == true { 54 | // if both parties agress with split decision, then split payment 55 | contract.status = ContractStatus::Completed; 56 | 57 | // To seller 58 | token::transfer( 59 | CpiContext::new_with_signer( 60 | token_program.to_account_info(), 61 | SplTransfer { 62 | from: source.to_account_info().clone(), 63 | to: seller_destination.to_account_info().clone(), 64 | authority: contract.to_account_info().clone(), 65 | }, 66 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 67 | ), 68 | ((total_balance - 2 * contract.dispute) * 45 / 100 + contract.dispute).try_into().unwrap(), 69 | )?; 70 | 71 | // To buyer 72 | token::transfer( 73 | CpiContext::new_with_signer( 74 | token_program.to_account_info(), 75 | SplTransfer { 76 | from: source.to_account_info().clone(), 77 | to: buyer_destination.to_account_info().clone(), 78 | authority: contract.to_account_info().clone(), 79 | }, 80 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 81 | ), 82 | ((total_balance - 2 * contract.dispute) * 45 / 100 + contract.dispute).try_into().unwrap(), 83 | )?; 84 | 85 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100).try_into().unwrap(); 86 | 87 | // To buyer referral 88 | if contract.buyer_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 89 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 90 | admin_amount -= buyer_referral_amount; 91 | 92 | token::transfer( 93 | CpiContext::new_with_signer( 94 | token_program.to_account_info(), 95 | SplTransfer { 96 | from: source.to_account_info().clone(), 97 | to: buyer_referral_destination.to_account_info().clone(), 98 | authority: contract.to_account_info().clone(), 99 | }, 100 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 101 | ), 102 | buyer_referral_amount, 103 | )?; 104 | } 105 | 106 | // To seller referral 107 | if contract.seller_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 108 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 109 | admin_amount -= seller_referral_amount; 110 | 111 | token::transfer( 112 | CpiContext::new_with_signer( 113 | token_program.to_account_info(), 114 | SplTransfer { 115 | from: source.to_account_info().clone(), 116 | to: seller_referral_destination.to_account_info().clone(), 117 | authority: contract.to_account_info().clone(), 118 | }, 119 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 120 | ), 121 | seller_referral_amount, 122 | )?; 123 | } 124 | 125 | // To admin 126 | token::transfer( 127 | CpiContext::new_with_signer( 128 | token_program.to_account_info(), 129 | SplTransfer { 130 | from: source.to_account_info().clone(), 131 | to: admin_destination.to_account_info().clone(), 132 | authority: contract.to_account_info().clone(), 133 | }, 134 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 135 | ), 136 | admin_amount, 137 | )?; 138 | } else { 139 | // Raise dispute if seller is not satisfied with split decision 140 | contract.status = ContractStatus::Dispute; 141 | } 142 | } else { 143 | // When both parties are satisfied with the result 144 | contract.status = ContractStatus::Completed; 145 | 146 | // To seller 147 | token::transfer( 148 | CpiContext::new_with_signer( 149 | token_program.to_account_info(), 150 | SplTransfer { 151 | from: source.to_account_info().clone(), 152 | to: seller_destination.to_account_info().clone(), 153 | authority: contract.to_account_info().clone(), 154 | }, 155 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 156 | ), 157 | ((total_balance - 2 * contract.dispute) * 90 / 100 + contract.dispute).try_into().unwrap(), 158 | )?; 159 | 160 | // To buyer 161 | token::transfer( 162 | CpiContext::new_with_signer( 163 | token_program.to_account_info(), 164 | SplTransfer { 165 | from: source.to_account_info().clone(), 166 | to: buyer_destination.to_account_info().clone(), 167 | authority: contract.to_account_info().clone(), 168 | }, 169 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 170 | ), 171 | contract.dispute, 172 | )?; 173 | 174 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100).try_into().unwrap(); 175 | 176 | // To buyer referral 177 | if contract.buyer_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 178 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 179 | admin_amount -= buyer_referral_amount; 180 | 181 | token::transfer( 182 | CpiContext::new_with_signer( 183 | token_program.to_account_info(), 184 | SplTransfer { 185 | from: source.to_account_info().clone(), 186 | to: buyer_referral_destination.to_account_info().clone(), 187 | authority: contract.to_account_info().clone(), 188 | }, 189 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 190 | ), 191 | buyer_referral_amount, 192 | )?; 193 | } 194 | 195 | // To seller referral 196 | if contract.seller_referral != anchor_lang::solana_program::pubkey!("3x9USDofKPb6rREu2dWe9rcvT4QMHQS1PrJ13WuZ1QL3") { 197 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 198 | admin_amount -= seller_referral_amount; 199 | 200 | token::transfer( 201 | CpiContext::new_with_signer( 202 | token_program.to_account_info(), 203 | SplTransfer { 204 | from: source.to_account_info().clone(), 205 | to: seller_referral_destination.to_account_info().clone(), 206 | authority: contract.to_account_info().clone(), 207 | }, 208 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 209 | ), 210 | seller_referral_amount, 211 | )?; 212 | } 213 | 214 | // To admin 215 | token::transfer( 216 | CpiContext::new_with_signer( 217 | token_program.to_account_info(), 218 | SplTransfer { 219 | from: source.to_account_info().clone(), 220 | to: admin_destination.to_account_info().clone(), 221 | authority: contract.to_account_info().clone(), 222 | }, 223 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 224 | ), 225 | admin_amount, 226 | )?; 227 | } 228 | } 229 | 230 | msg!("Funds released by seller successfully!"); 231 | Ok(()) 232 | } 233 | 234 | #[derive(Accounts)] 235 | #[instruction(contract_id: String)] 236 | pub struct SellerApproveContext<'info> { 237 | pub pay_token_mint: Account<'info, Mint>, // Define the mint account 238 | 239 | #[account(mut)] 240 | pub seller: Signer<'info>, 241 | 242 | pub seller_referral: SystemAccount<'info>, 243 | pub buyer_referral: SystemAccount<'info>, 244 | 245 | #[account( 246 | mut, 247 | seeds = [ 248 | CONTRACT_SEED.as_bytes(), 249 | &contract_id.as_bytes() 250 | ], 251 | bump, 252 | )] 253 | pub contract: Account<'info, Contract>, 254 | 255 | #[account( 256 | mut, 257 | associated_token::mint = pay_token_mint, 258 | associated_token::authority = contract.buyer, 259 | )] 260 | pub buyer_ata: Box>, 261 | 262 | #[account( 263 | mut, 264 | associated_token::mint = pay_token_mint, 265 | associated_token::authority = contract.seller, 266 | )] 267 | pub seller_ata: Box>, 268 | 269 | #[account( 270 | init_if_needed, 271 | payer = seller, 272 | associated_token::mint = pay_token_mint, 273 | associated_token::authority = seller_referral, 274 | )] 275 | pub seller_referral_ata: Box>, 276 | 277 | #[account( 278 | init_if_needed, 279 | payer = seller, 280 | associated_token::mint = pay_token_mint, 281 | associated_token::authority = buyer_referral, 282 | )] 283 | pub buyer_referral_ata: Box>, 284 | 285 | #[account( 286 | mut, 287 | associated_token::mint = pay_token_mint, 288 | associated_token::authority = ADMIN_ADDRESS, 289 | )] 290 | pub admin_ata: Box>, 291 | 292 | #[account( 293 | mut, 294 | associated_token::mint = pay_token_mint, 295 | associated_token::authority = contract, 296 | )] 297 | pub contract_ata: Box>, 298 | 299 | 300 | pub token_program: Program<'info, Token>, 301 | pub associated_token_program: Program<'info, AssociatedToken>, 302 | pub system_program: Program<'info, System>, 303 | } 304 | -------------------------------------------------------------------------------- /programs/gig-basic-contract/src/instructions/admin_approve.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{ self, Mint, Token, TokenAccount, Transfer as SplTransfer } 5 | }; 6 | use std::mem::size_of; 7 | 8 | use crate::state::contract::*; 9 | use crate::constants::{ 10 | CONTRACT_SEED, 11 | ADMIN_ADDRESS, 12 | PAY_TOKEN_MINT_ADDRESS 13 | }; 14 | use crate::errors::{ 15 | GigContractError 16 | }; 17 | 18 | 19 | pub fn admin_approve( 20 | ctx: Context, 21 | contract_id: String, 22 | decision: u8 // 0 for both ok by default, 1 for seller, 2 for buyer, 3 for split 23 | ) -> Result<()> { 24 | msg!("Releasing funds on seller side!"); 25 | 26 | let contract = &mut ctx.accounts.contract; 27 | 28 | // Check if the signer is a correct admin 29 | require_keys_eq!(ctx.accounts.admin.key(), ADMIN_ADDRESS, GigContractError::InvalidAdmin); 30 | 31 | // Check if the contract is pending which means one of two parties approved. 32 | require!(contract.status == ContractStatus::Pending || contract.status == ContractStatus::Dispute, GigContractError::NotReadyYet); 33 | 34 | let token_program = &ctx.accounts.token_program; 35 | let source = &ctx.accounts.contract_ata; 36 | let seller_destination = &ctx.accounts.seller_ata; 37 | let buyer_destination = &ctx.accounts.buyer_ata; 38 | let admin_destination = &ctx.accounts.admin_ata; 39 | 40 | // contract.status = ContractStatus::Completed; 41 | contract.admin_approved = true; 42 | 43 | let total_balance = source.amount; 44 | 45 | // If buyer is not responding, admin will approve with seller. Admin will get buyer's dispute fee 46 | if contract.status == ContractStatus::Pending { 47 | // To seller 48 | token::transfer( 49 | CpiContext::new_with_signer( 50 | token_program.to_account_info(), 51 | SplTransfer { 52 | from: source.to_account_info().clone(), 53 | to: seller_destination.to_account_info().clone(), 54 | authority: contract.to_account_info().clone(), 55 | }, 56 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 57 | ), 58 | ((total_balance - 2 * contract.dispute) * 90 / 100 + contract.dispute).try_into().unwrap(), 59 | )?; 60 | 61 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100 + contract.dispute).try_into().unwrap(); 62 | 63 | // To buyer referral 64 | if let Some(buyer_referral_ata) = &ctx.accounts.buyer_referral_ata { 65 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 66 | admin_amount -= buyer_referral_amount; 67 | 68 | token::transfer( 69 | CpiContext::new_with_signer( 70 | token_program.to_account_info(), 71 | SplTransfer { 72 | from: source.to_account_info().clone(), 73 | to: buyer_referral_ata.to_account_info().clone(), 74 | authority: contract.to_account_info().clone(), 75 | }, 76 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 77 | ), 78 | buyer_referral_amount, 79 | )?; 80 | } 81 | 82 | // To seller referral 83 | if let Some(seller_referral_ata) = &ctx.accounts.seller_referral_ata { 84 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 85 | admin_amount -= seller_referral_amount; 86 | 87 | token::transfer( 88 | CpiContext::new_with_signer( 89 | token_program.to_account_info(), 90 | SplTransfer { 91 | from: source.to_account_info().clone(), 92 | to: seller_referral_ata.to_account_info().clone(), 93 | authority: contract.to_account_info().clone(), 94 | }, 95 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 96 | ), 97 | seller_referral_amount, 98 | )?; 99 | } 100 | 101 | // To admin 102 | token::transfer( 103 | CpiContext::new_with_signer( 104 | token_program.to_account_info(), 105 | SplTransfer { 106 | from: source.to_account_info().clone(), 107 | to: admin_destination.to_account_info().clone(), 108 | authority: contract.to_account_info().clone(), 109 | }, 110 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 111 | ), 112 | admin_amount, 113 | )?; 114 | } else { 115 | // if dispute, perform action based on decision value 116 | // 0 for both ok by default, 1 for seller, 2 for buyer, 3 for split 117 | 118 | match decision { 119 | 1 => { // admin agrees with seller 120 | // transfer payment to seller and admin gets buyer's dispute fee 121 | contract.status = ContractStatus::Completed; 122 | 123 | // To seller 124 | token::transfer( 125 | CpiContext::new_with_signer( 126 | token_program.to_account_info(), 127 | SplTransfer { 128 | from: source.to_account_info().clone(), 129 | to: seller_destination.to_account_info().clone(), 130 | authority: contract.to_account_info().clone(), 131 | }, 132 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 133 | ), 134 | ((total_balance - 2 * contract.dispute) * 90 / 100 + contract.dispute).try_into().unwrap(), 135 | )?; 136 | 137 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100 + contract.dispute).try_into().unwrap(); 138 | 139 | // To buyer referral 140 | if let Some(buyer_referral_ata) = &ctx.accounts.buyer_referral_ata { 141 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 142 | admin_amount -= buyer_referral_amount; 143 | 144 | token::transfer( 145 | CpiContext::new_with_signer( 146 | token_program.to_account_info(), 147 | SplTransfer { 148 | from: source.to_account_info().clone(), 149 | to: buyer_referral_ata.to_account_info().clone(), 150 | authority: contract.to_account_info().clone(), 151 | }, 152 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 153 | ), 154 | buyer_referral_amount, 155 | )?; 156 | } 157 | 158 | // To seller referral 159 | if let Some(seller_referral_ata) = &ctx.accounts.seller_referral_ata { 160 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 161 | admin_amount -= seller_referral_amount; 162 | 163 | token::transfer( 164 | CpiContext::new_with_signer( 165 | token_program.to_account_info(), 166 | SplTransfer { 167 | from: source.to_account_info().clone(), 168 | to: seller_referral_ata.to_account_info().clone(), 169 | authority: contract.to_account_info().clone(), 170 | }, 171 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 172 | ), 173 | seller_referral_amount, 174 | )?; 175 | } 176 | 177 | // To admin 178 | token::transfer( 179 | CpiContext::new_with_signer( 180 | token_program.to_account_info(), 181 | SplTransfer { 182 | from: source.to_account_info().clone(), 183 | to: admin_destination.to_account_info().clone(), 184 | authority: contract.to_account_info().clone(), 185 | }, 186 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 187 | ), 188 | admin_amount, 189 | )?; 190 | }, 191 | 2 => { 192 | // admin agrees with buyer 193 | // transfer payment to buyer and admin gets seller's dispute fee 194 | contract.status = ContractStatus::Completed; 195 | 196 | // To buyer 197 | token::transfer( 198 | CpiContext::new_with_signer( 199 | token_program.to_account_info(), 200 | SplTransfer { 201 | from: source.to_account_info().clone(), 202 | to: buyer_destination.to_account_info().clone(), 203 | authority: contract.to_account_info().clone(), 204 | }, 205 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 206 | ), 207 | ((total_balance - 2 * contract.dispute) * 90 / 100 + contract.dispute).try_into().unwrap(), 208 | )?; 209 | 210 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100 + contract.dispute).try_into().unwrap(); 211 | 212 | // To buyer referral 213 | if let Some(buyer_referral_ata) = &ctx.accounts.buyer_referral_ata { 214 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 215 | admin_amount -= buyer_referral_amount; 216 | 217 | token::transfer( 218 | CpiContext::new_with_signer( 219 | token_program.to_account_info(), 220 | SplTransfer { 221 | from: source.to_account_info().clone(), 222 | to: buyer_referral_ata.to_account_info().clone(), 223 | authority: contract.to_account_info().clone(), 224 | }, 225 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 226 | ), 227 | buyer_referral_amount, 228 | )?; 229 | } 230 | 231 | // To seller referral 232 | if let Some(seller_referral_ata) = &ctx.accounts.seller_referral_ata { 233 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 234 | admin_amount -= seller_referral_amount; 235 | 236 | token::transfer( 237 | CpiContext::new_with_signer( 238 | token_program.to_account_info(), 239 | SplTransfer { 240 | from: source.to_account_info().clone(), 241 | to: seller_referral_ata.to_account_info().clone(), 242 | authority: contract.to_account_info().clone(), 243 | }, 244 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 245 | ), 246 | seller_referral_amount, 247 | )?; 248 | } 249 | 250 | // To admin 251 | token::transfer( 252 | CpiContext::new_with_signer( 253 | token_program.to_account_info(), 254 | SplTransfer { 255 | from: source.to_account_info().clone(), 256 | to: admin_destination.to_account_info().clone(), 257 | authority: contract.to_account_info().clone(), 258 | }, 259 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 260 | ), 261 | admin_amount, 262 | )?; 263 | }, 264 | _ => { 265 | // admin agrees with split dicision 266 | // split payment and admin gets half of dispute fee from both parties 267 | contract.status = ContractStatus::Completed; 268 | 269 | // To seller 270 | token::transfer( 271 | CpiContext::new_with_signer( 272 | token_program.to_account_info(), 273 | SplTransfer { 274 | from: source.to_account_info().clone(), 275 | to: seller_destination.to_account_info().clone(), 276 | authority: contract.to_account_info().clone(), 277 | }, 278 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 279 | ), 280 | ((total_balance - 2 * contract.dispute) * 45 / 100 + contract.dispute / 2).try_into().unwrap(), 281 | )?; 282 | 283 | // To buyer 284 | token::transfer( 285 | CpiContext::new_with_signer( 286 | token_program.to_account_info(), 287 | SplTransfer { 288 | from: source.to_account_info().clone(), 289 | to: buyer_destination.to_account_info().clone(), 290 | authority: contract.to_account_info().clone(), 291 | }, 292 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 293 | ), 294 | ((total_balance - 2 * contract.dispute) * 45 / 100 + contract.dispute / 2).try_into().unwrap(), 295 | )?; 296 | 297 | let mut admin_amount: u64 = ((total_balance - 2 * contract.dispute ) * 10 / 100 + contract.dispute).try_into().unwrap(); 298 | 299 | // To buyer referral 300 | if let Some(buyer_referral_ata) = &ctx.accounts.buyer_referral_ata { 301 | let buyer_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 302 | admin_amount -= buyer_referral_amount; 303 | 304 | token::transfer( 305 | CpiContext::new_with_signer( 306 | token_program.to_account_info(), 307 | SplTransfer { 308 | from: source.to_account_info().clone(), 309 | to: buyer_referral_ata.to_account_info().clone(), 310 | authority: contract.to_account_info().clone(), 311 | }, 312 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 313 | ), 314 | buyer_referral_amount, 315 | )?; 316 | } 317 | 318 | // To seller referral 319 | if let Some(seller_referral_ata) = &ctx.accounts.seller_referral_ata { 320 | let seller_referral_amount: u64 = ((total_balance - 2 * contract.dispute ) * 1 / 100).try_into().unwrap(); 321 | admin_amount -= seller_referral_amount; 322 | 323 | token::transfer( 324 | CpiContext::new_with_signer( 325 | token_program.to_account_info(), 326 | SplTransfer { 327 | from: source.to_account_info().clone(), 328 | to: seller_referral_ata.to_account_info().clone(), 329 | authority: contract.to_account_info().clone(), 330 | }, 331 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 332 | ), 333 | seller_referral_amount, 334 | )?; 335 | } 336 | 337 | // To admin 338 | token::transfer( 339 | CpiContext::new_with_signer( 340 | token_program.to_account_info(), 341 | SplTransfer { 342 | from: source.to_account_info().clone(), 343 | to: admin_destination.to_account_info().clone(), 344 | authority: contract.to_account_info().clone(), 345 | }, 346 | &[&[CONTRACT_SEED.as_bytes(), &contract.contract_id.as_bytes(), &[ctx.bumps.contract]]], 347 | ), 348 | admin_amount, 349 | )?; 350 | } 351 | } 352 | } 353 | 354 | msg!("Funds released by admin successfully!"); 355 | Ok(()) 356 | } 357 | 358 | #[derive(Accounts)] 359 | #[instruction(contract_id: String)] 360 | pub struct AdminApproveContext<'info> { 361 | #[account(mut)] 362 | pub admin: Signer<'info>, 363 | 364 | #[account( 365 | mut, 366 | seeds = [ 367 | CONTRACT_SEED.as_bytes(), 368 | &contract_id.as_bytes() 369 | ], 370 | bump, 371 | )] 372 | pub contract: Account<'info, Contract>, 373 | 374 | #[account( 375 | mut, 376 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 377 | associated_token::authority = contract.seller, 378 | )] 379 | pub seller_ata: Account<'info, TokenAccount>, 380 | 381 | // Optional 382 | #[account( 383 | mut, 384 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 385 | associated_token::authority = contract.seller_referral, 386 | )] 387 | pub seller_referral_ata: Option>, 388 | 389 | #[account( 390 | mut, 391 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 392 | associated_token::authority = contract.buyer, 393 | )] 394 | pub buyer_ata: Account<'info, TokenAccount>, 395 | 396 | // Optional 397 | #[account( 398 | mut, 399 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 400 | associated_token::authority = contract.buyer_referral, 401 | )] 402 | pub buyer_referral_ata: Option>, 403 | 404 | #[account( 405 | mut, 406 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 407 | associated_token::authority = ADMIN_ADDRESS, 408 | )] 409 | pub admin_ata: Account<'info, TokenAccount>, 410 | 411 | 412 | #[account( 413 | mut, 414 | associated_token::mint = PAY_TOKEN_MINT_ADDRESS, 415 | associated_token::authority = contract, 416 | )] 417 | pub contract_ata: Account<'info, TokenAccount>, 418 | 419 | pub token_program: Program<'info, Token>, 420 | pub associated_token_program: Program<'info, AssociatedToken>, 421 | pub system_program: Program<'info, System>, 422 | } 423 | -------------------------------------------------------------------------------- /tests/gig-basic-contract.test: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor@0.30.1"; 2 | import { Program, Wallet } from "@coral-xyz/anchor@0.30.1"; 3 | import { expect, assert } from "chai"; 4 | import { 5 | getOrCreateAssociatedTokenAccount, 6 | getAssociatedTokenAddressSync, 7 | transfer, 8 | TOKEN_PROGRAM_ID, 9 | ASSOCIATED_TOKEN_PROGRAM_ID, 10 | } from "@solana/spl-token"; 11 | import { 12 | Keypair, 13 | PublicKey, 14 | SystemProgram, 15 | SYSVAR_RENT_PUBKEY, 16 | Transaction, 17 | sendAndConfirmTransaction, 18 | LAMPORTS_PER_SOL, 19 | } from "@solana/web3.js"; 20 | import { GigBasicContract } from "../target/types/gig_basic_contract"; 21 | import bs58 from "bs58"; 22 | import { v4 as uuid } from "uuid"; 23 | import secret from "~/.config/solana/id.json"; 24 | 25 | describe("gig-basic-contract", () => { 26 | // Configure the client to use the local cluster. 27 | const provider = anchor.AnchorProvider.env(); 28 | 29 | anchor.setProvider(anchor.AnchorProvider.env()); 30 | const program = anchor.workspace 31 | .GigBasicContract as Program; 32 | 33 | const connection = provider.connection; 34 | 35 | const CONTRACT_SEED = "gig_contract"; 36 | const authKp = Keypair.fromSecretKey(new Uint8Array(secret)); 37 | 38 | // const firstKp = new Keypair(); 39 | // const secondKp = new Keypair(); 40 | const firstKp = Keypair.fromSecretKey( 41 | Uint8Array.from( 42 | bs58.decode( 43 | "4bnMLLijWtwRMGanFqTtBpMmZ6u5SngacrjmeALYZikLCq5gTzZpvKY8jyh7y9GWfoMG8hUccCrwHytkwy1imYXh" 44 | ) 45 | ) 46 | ); // 1sol-account 47 | const secondKp = Keypair.fromSecretKey( 48 | Uint8Array.from( 49 | bs58.decode( 50 | "5sspNdxSSRfpj5pSoZULYmrYdMhuYn2vqN4ZzwLiJKLUAuyKmsf8J9ueA8o7FMR5Dr2dUtZ8sdpp6cSEHVgnfepq" 51 | ) 52 | ) 53 | ); // Test Account 54 | 55 | let payTokenMint = new PublicKey( 56 | "7FctSfSZ9GonfMrybp45hzoQyU71CEjjZFxxoSzqKWT" 57 | ); // BPT mint address 58 | 59 | let decimal = 8; 60 | 61 | // let contractAddress: any; 62 | let contractAddress = new PublicKey( 63 | "H65BKYrJMz4mK5dmT7GGVhKTAk6Va8wDuZgBRV4UKQ95" 64 | ); 65 | // let contractBump: any; 66 | let contractBump = 254; 67 | 68 | let authAta: any; 69 | let firstAta: any; 70 | let secondAta: any; 71 | let contractAta: any; 72 | let contractId: any = "a4fc3529"; 73 | 74 | let buyerReferral = new PublicKey( 75 | "At1PNJxjhr9NrThvDGCkNA17Q57ceWZqXe6pdbYb15T5" 76 | ); 77 | 78 | let sellerReferral = new PublicKey( 79 | "E2kkuEFZktytswKCYYsXyKx483vfM2qqj1zPQDer4djg" 80 | ); 81 | 82 | let buyerReferralAta: any; 83 | let sellerReferralAta: any; 84 | 85 | // it("[Success] Fetch all contracts!", async () => { 86 | // try { 87 | // // Fetch the pool account and assert the values 88 | // const allContractAccount = await program.account.contract.all(); 89 | 90 | // console.log("allContractAccount:", allContractAccount); 91 | 92 | // } catch (error) { 93 | // console.log("Error while fetching all pools:", error); 94 | // } 95 | // }); 96 | 97 | it("[Success] Fetch all hourly contracts!", async () => { 98 | try { 99 | // Fetch the pool account and assert the values 100 | const allContractAccount = await program.account.hourlyContract.all(); 101 | 102 | console.log("all hourly ContractAccount:", allContractAccount); 103 | 104 | } catch (error) { 105 | console.log("Error while fetching all hourly pools:", error); 106 | } 107 | }); 108 | 109 | it("[Success] Set Up!", async () => { 110 | try { 111 | authAta = await getOrCreateAssociatedTokenAccount( 112 | program.provider.connection, 113 | authKp, 114 | payTokenMint, 115 | authKp.publicKey 116 | ); 117 | 118 | contractAta = await getOrCreateAssociatedTokenAccount( 119 | program.provider.connection, 120 | authKp, 121 | payTokenMint, 122 | contractAddress, 123 | true 124 | ); 125 | 126 | console.log("contractAta", contractAta); 127 | 128 | // await transfer( 129 | // program.provider.connection, 130 | // authKp, 131 | // authAta.address, 132 | // treasuryAta.address, 133 | // authKp.publicKey, 134 | // treasuryAmount 135 | // ); 136 | 137 | firstAta = await getOrCreateAssociatedTokenAccount( 138 | program.provider.connection, 139 | authKp, 140 | payTokenMint, 141 | firstKp.publicKey 142 | ); 143 | 144 | // await transfer( 145 | // program.provider.connection, 146 | // authKp, 147 | // authAta.address, 148 | // firstAta.address, 149 | // authKp.publicKey, 150 | // firstAmount 151 | // ); 152 | 153 | secondAta = await getOrCreateAssociatedTokenAccount( 154 | program.provider.connection, 155 | secondKp, 156 | payTokenMint, 157 | secondKp.publicKey 158 | ); 159 | 160 | buyerReferralAta = await getOrCreateAssociatedTokenAccount( 161 | program.provider.connection, 162 | authKp, 163 | payTokenMint, 164 | buyerReferral 165 | ); 166 | 167 | sellerReferralAta = await getOrCreateAssociatedTokenAccount( 168 | program.provider.connection, 169 | authKp, 170 | payTokenMint, 171 | sellerReferral 172 | ); 173 | 174 | // await transfer( 175 | // program.provider.connection, 176 | // authKp, 177 | // authAta.address, 178 | // secondAta.address, 179 | // authKp.publicKey, 180 | // secondAmount 181 | // ); 182 | 183 | const authTokenAccount = 184 | await program.provider.connection.getTokenAccountBalance( 185 | authAta.address 186 | ); 187 | 188 | console.log("authTokenAccount", authTokenAccount.value.amount); 189 | 190 | const firstTokenAccount = 191 | await program.provider.connection.getTokenAccountBalance( 192 | firstAta.address 193 | ); 194 | 195 | console.log("firstTokenAccount", firstTokenAccount.value.amount); 196 | 197 | const secondTokenAccount = 198 | await program.provider.connection.getTokenAccountBalance( 199 | secondAta.address 200 | ); 201 | 202 | console.log("secondTokenAccount", secondTokenAccount.value.amount); 203 | } catch (error) { 204 | console.log("Error while setting up:", error); 205 | } 206 | }); 207 | 208 | // // Testing for all satisfied case 209 | // it("1-[Failure-Invalid dispute amount] Create a new contract!", async () => { 210 | // try { 211 | // // Create a new uuid to use as a new contract id 212 | // contractId = uuid().slice(0, 8); 213 | // console.log("new contractId:", contractId); 214 | 215 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 216 | // const dispute = new anchor.BN(0.4 * Math.pow(10, decimal)); // 0.4 BPT token; // 0.4 USDC shoulb be 50 cent 217 | // const deadline = Math.floor(Date.now() / 1000) + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 218 | 219 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 220 | // [ 221 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 222 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 223 | // ], 224 | // program.programId 225 | // ); 226 | 227 | 228 | // contractAddress = contract; 229 | // contractBump = bump; 230 | // console.log("contractAddress", contractAddress); 231 | // console.log("contractBump", contractBump); 232 | 233 | // contractAta = await getOrCreateAssociatedTokenAccount( 234 | // program.provider.connection, 235 | // authKp, 236 | // payTokenMint, 237 | // contractAddress, 238 | // true 239 | // ); 240 | 241 | // // Call startContract function 242 | // const tx = await program.methods 243 | // .startContract( 244 | // contractId, 245 | // amount, 246 | // dispute, 247 | // deadline, 248 | 249 | // ) 250 | // .accounts({ 251 | // buyer: firstKp.publicKey, 252 | // contract, 253 | // seller: secondKp.publicKey, 254 | // buyerAta: firstAta.address, 255 | // contractAta: contractAta.address, 256 | // tokenProgram: TOKEN_PROGRAM_ID, 257 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 258 | // systemProgram: anchor.web3.SystemProgram.programId, 259 | // rent: SYSVAR_RENT_PUBKEY, 260 | // }) 261 | 262 | // .signers([firstKp]) 263 | // .rpc(); 264 | // // .rpc({ skipPreflight: true }); 265 | 266 | 267 | // console.log("Your transaction signature for creating a new contract", tx); 268 | 269 | // // Fetch the contract account and assert the values 270 | // const contractAccount = await program.account.contract.fetch(contract); 271 | 272 | // console.log("new contract account:", contractAccount); 273 | 274 | // } catch (error) { 275 | // // console.log("Error while creating a new contract:", error); 276 | // assert.equal(error.error.errorCode.code, "InvalidDisputeAmount"); 277 | // assert.equal(error.error.errorCode.number, 6004); 278 | // assert.equal(error.error.errorMessage, "Dispute Amount should be 50 cent!"); 279 | // } 280 | // }); 281 | ///===========================// 282 | // it("1-[Success] Create a new contract on buyer side!", async () => { 283 | // try { 284 | // // Create a new uuid to use as a new contract id 285 | // contractId = uuid().slice(0, 8); 286 | // console.log("new contractId:", contractId); 287 | 288 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 289 | // const dispute = new anchor.BN(0.5 * Math.pow(10, decimal)); // 0.5 BPT token; // 0.5 USDC 50 cent 290 | // const deadline = Math.floor(Date.now() / 1000) + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 291 | 292 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 293 | // [ 294 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 295 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 296 | // ], 297 | // program.programId 298 | // ); 299 | 300 | 301 | // contractAddress = contract; 302 | // contractBump = bump; 303 | // console.log("contractAddress", contractAddress); 304 | // console.log("contractBump", contractBump); 305 | 306 | // contractAta = await getOrCreateAssociatedTokenAccount( 307 | // program.provider.connection, 308 | // authKp, 309 | // payTokenMint, 310 | // contractAddress, 311 | // true 312 | // ); 313 | 314 | // // Call startContract function 315 | // const tx = await program.methods 316 | // .startContractOnBuyer( 317 | // contractId, 318 | // amount, 319 | // dispute, 320 | // deadline, 321 | // ) 322 | // .accounts({ 323 | // buyer: firstKp.publicKey, 324 | // contract, 325 | // seller: secondKp.publicKey, 326 | // buyerReferral, 327 | // buyerAta: firstAta.address, 328 | // payTokenMint, 329 | // contractAta: contractAta.address, 330 | // tokenProgram: TOKEN_PROGRAM_ID, 331 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 332 | // systemProgram: anchor.web3.SystemProgram.programId, 333 | // rent: SYSVAR_RENT_PUBKEY, 334 | // }) 335 | 336 | // .signers([firstKp]) 337 | // .rpc(); 338 | // // .rpc({ skipPreflight: true }); 339 | 340 | 341 | // console.log("Your transaction signature for creating a new contract", tx); 342 | 343 | // // Fetch the contract account and assert the values 344 | // const contractAccount = await program.account.contract.fetch(contract); 345 | 346 | // console.log("new contract account:", contractAccount); 347 | 348 | // } catch (error) { 349 | // console.log("Error while creating a new contract:", error); 350 | // } 351 | // }); 352 | 353 | ///===========================// 354 | // it("1-[Success] Activate the contract!", async () => { 355 | // try { 356 | // let contractAccount = await program.account.contract.fetch(contractAddress); 357 | // console.log("new contract before activating:", contractAccount); 358 | 359 | // // Call the buy_tickets function 360 | // const tx = await program.methods 361 | // .activateContract(contractId, true) 362 | // .accounts({ 363 | // contract: contractAddress, 364 | // seller: secondKp.publicKey, 365 | // sellerAta: secondAta.address, 366 | // sellerReferral: null, 367 | // contractAta: contractAta.address, 368 | // tokenProgram: TOKEN_PROGRAM_ID, 369 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 370 | // systemProgram: anchor.web3.SystemProgram.programId, 371 | // }) 372 | // .signers([secondKp]) 373 | // .rpc({ skipPreflight: true }); 374 | 375 | // console.log("Your transaction signature for activating the contract:", tx); 376 | 377 | // contractAccount = await program.account.contract.fetch(contractAddress); 378 | 379 | // console.log("new contract after activating:", contractAccount); 380 | // } catch (error) { 381 | // console.log("Error while activating contract!:", error); 382 | // } 383 | // }); 384 | 385 | // it("1-[Failure-invalid buyer] Approve by buyer(client)!", async () => { 386 | // try { 387 | // let contractAccount = await program.account.contract.fetch(contractAddress); 388 | // console.log("new contract before approving on buyer!:", contractAccount); 389 | 390 | // const tx = await program.methods 391 | // .buyerApprove(contractId, false) 392 | // .accounts({ 393 | // contract: contractAddress, 394 | // buyer: secondKp.publicKey, 395 | // sellerAta: secondAta.address, 396 | // buyerAta: firstAta.address, 397 | // adminAta: authAta.address, 398 | // contractAta: contractAta.address, 399 | // tokenProgram: TOKEN_PROGRAM_ID, 400 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 401 | // systemProgram: anchor.web3.SystemProgram.programId, 402 | // }) 403 | // .signers([secondKp]) 404 | // .rpc(); 405 | 406 | // console.log("Your transaction signature for approving on buyer:", tx); 407 | 408 | // contractAccount = await program.account.contract.fetch(contractAddress); 409 | 410 | // console.log("new contract after approving on buyer!:", contractAccount); 411 | // } catch (error) { 412 | // // console.log("Error while approving on buyer!:", error); 413 | // assert.equal(error.error.errorCode.code, "InvalidBuyer"); 414 | // assert.equal(error.error.errorCode.number, 6002); 415 | // assert.equal(error.error.errorMessage, "Invalid buyer is trying to release funds!"); 416 | // } 417 | // }); 418 | 419 | ///===========================// 420 | // it("1-[Success-Satisfied(no split)] Approve by buyer(client)!", async () => { 421 | // try { 422 | // let contractAccount = await program.account.contract.fetch(contractAddress); 423 | // console.log("new contract before approving on buyer!:", contractAccount); 424 | 425 | // const tx = await program.methods 426 | // .buyerApprove(contractId, false) 427 | // .accounts({ 428 | // contract: contractAddress, 429 | // buyer: firstKp.publicKey, 430 | // sellerAta: secondAta.address, 431 | // buyerAta: firstAta.address, 432 | // adminAta: authAta.address, 433 | // contractAta: contractAta.address, 434 | // tokenProgram: TOKEN_PROGRAM_ID, 435 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 436 | // systemProgram: anchor.web3.SystemProgram.programId, 437 | // }) 438 | // .signers([firstKp]) 439 | // .rpc({ skipPreflight: true }); 440 | 441 | // console.log("Your transaction signature for approving on buyer:", tx); 442 | 443 | // contractAccount = await program.account.contract.fetch(contractAddress); 444 | 445 | // console.log("new contract after approving on buyer!:", contractAccount); 446 | // } catch (error) { 447 | // console.log("Error while approving on buyer!:", error); 448 | // } 449 | // }); 450 | 451 | // it("1-[Failure-invalid seller] Approve by seller(freelancer)!", async () => { 452 | // try { 453 | // let contractAccount = await program.account.contract.fetch(contractAddress); 454 | // console.log("new contract before approving on seller!:", contractAccount); 455 | 456 | // const tx = await program.methods 457 | // .sellerApprove(contractId, false) 458 | // .accounts({ 459 | // contract: contractAddress, 460 | // seller: firstKp.publicKey, 461 | // sellerAta: secondAta.address, 462 | // buyerAta: firstAta.address, 463 | // adminAta: authAta.address, 464 | // contractAta: contractAta.address, 465 | // tokenProgram: TOKEN_PROGRAM_ID, 466 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 467 | // systemProgram: anchor.web3.SystemProgram.programId, 468 | // }) 469 | // .signers([firstKp]) 470 | // .rpc(); 471 | 472 | // console.log("Your transaction signature for approving on seller:", tx); 473 | 474 | // contractAccount = await program.account.contract.fetch(contractAddress); 475 | 476 | // console.log("new contract after approving on seller!:", contractAccount); 477 | // } catch (error) { 478 | // // console.log("Error while approving on seller!:", error); 479 | // assert.equal(error.error.errorCode.code, "InvalidSeller"); 480 | // assert.equal(error.error.errorCode.number, 6000); 481 | // assert.equal(error.error.errorMessage, "Invalid seller is trying to release funds!"); 482 | // } 483 | // }); 484 | 485 | ///===========================// 486 | // it("1-[Success-No split] Approve by seller(freelancer)!", async () => { 487 | // try { 488 | // let contractAccount = await program.account.contract.fetch(contractAddress); 489 | // console.log("new contract before approving on seller!:", contractAccount); 490 | 491 | // const tx = await program.methods 492 | // .sellerApprove(contractId, false) 493 | // .accounts({ 494 | // contract: contractAddress, 495 | // seller: secondKp.publicKey, 496 | // sellerAta: secondAta.address, 497 | // buyerAta: firstAta.address, 498 | // adminAta: authAta.address, 499 | // contractAta: contractAta.address, 500 | // buyerReferralAta: buyerReferralAta.address, 501 | // sellerReferralAta: null, // sellerReferralAta.address, 502 | // tokenProgram: TOKEN_PROGRAM_ID, 503 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 504 | // systemProgram: anchor.web3.SystemProgram.programId, 505 | // }) 506 | // .signers([secondKp]) 507 | // .rpc(); 508 | 509 | // console.log("Your transaction signature for approving on seller:", tx); 510 | 511 | // contractAccount = await program.account.contract.fetch(contractAddress); 512 | 513 | // console.log("new contract after approving on seller!:", contractAccount); 514 | // } catch (error) { 515 | // console.log("Error while approving on seller!:", error); 516 | // } 517 | // }); 518 | 519 | // it("1-[Failure] Approve by admin!", async () => { 520 | // try { 521 | // let contractAccount = await program.account.contract.fetch(contractAddress); 522 | // console.log("new contract before approving on admin!:", contractAccount); 523 | 524 | // const tx = await program.methods 525 | // .adminApprove(contractId, 0) 526 | // .accounts({ 527 | // contract: contractAddress, 528 | // admin: authKp.publicKey, 529 | // sellerAta: secondAta.address, 530 | // buyerAta: firstAta.address, 531 | // adminAta: authAta.address, 532 | // contractAta: contractAta.address, 533 | // tokenProgram: TOKEN_PROGRAM_ID, 534 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 535 | // systemProgram: anchor.web3.SystemProgram.programId, 536 | // }) 537 | // .signers([authKp]) 538 | // .rpc(); 539 | 540 | // console.log("Your transaction signature for approving on admin:", tx); 541 | 542 | // contractAccount = await program.account.contract.fetch(contractAddress); 543 | 544 | // console.log("new contract after approving on admin!:", contractAccount); 545 | // } catch (error) { 546 | // // console.log("Error while approving on admin!:", error); 547 | // assert.equal(error.error.errorCode.code, "NotReadyYet"); 548 | // assert.equal(error.error.errorCode.number, 6006); 549 | // assert.equal(error.error.errorMessage, "Contract is not pending or disputed yet so admin can't approve now or already completed!"); 550 | // } 551 | // }); 552 | 553 | // // Testing for invalid seller activate 554 | // it("2-[Success] Create a new contract!", async () => { 555 | // try { 556 | // // Create a new uuid to use as a new contract id 557 | // contractId = uuid().slice(0, 8); 558 | // console.log("new contractId:", contractId); 559 | 560 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 561 | // const dispute = new anchor.BN(0.5 * Math.pow(10, decimal)); // 0.5 BPT token; // 0.5 USDC 562 | // const deadline = Math.floor(Date.now() / 1000); // + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 563 | 564 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 565 | // [ 566 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 567 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 568 | // ], 569 | // program.programId 570 | // ); 571 | 572 | 573 | // contractAddress = contract; 574 | // contractBump = bump; 575 | // console.log("contractAddress", contractAddress); 576 | // console.log("contractBump", contractBump); 577 | 578 | // contractAta = await getOrCreateAssociatedTokenAccount( 579 | // program.provider.connection, 580 | // authKp, 581 | // payTokenMint, 582 | // contractAddress, 583 | // true 584 | // ); 585 | 586 | // // Call startContract function 587 | // const tx = await program.methods 588 | // .startContract( 589 | // contractId, 590 | // amount, 591 | // dispute, 592 | // deadline, 593 | 594 | // ) 595 | // .accounts({ 596 | // buyer: firstKp.publicKey, 597 | // contract, 598 | // seller: secondKp.publicKey, 599 | // buyerAta: firstAta.address, 600 | // contractAta: contractAta.address, 601 | // tokenProgram: TOKEN_PROGRAM_ID, 602 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 603 | // systemProgram: anchor.web3.SystemProgram.programId, 604 | // rent: SYSVAR_RENT_PUBKEY, 605 | // }) 606 | 607 | // .signers([firstKp]) 608 | // .rpc(); 609 | // // .rpc({ skipPreflight: true }); 610 | 611 | 612 | // console.log("Your transaction signature for creating a new contract", tx); 613 | 614 | // // Fetch the contract account and assert the values 615 | // const contractAccount = await program.account.contract.fetch(contract); 616 | 617 | // console.log("new contract account:", contractAccount); 618 | 619 | // } catch (error) { 620 | // console.log("Error while creating a new contract:", error); 621 | // } 622 | // }); 623 | 624 | // it("2-[Failure-invalid seller-activate] Activate the contract!", async () => { 625 | // try { 626 | // let contractAccount = await program.account.contract.fetch(contractAddress); 627 | // console.log("new contract before activating:", contractAccount); 628 | 629 | // // Call the buy_tickets function 630 | // const tx = await program.methods 631 | // .activateContract(contractId) 632 | // .accounts({ 633 | // contract: contractAddress, 634 | // seller: firstKp.publicKey, 635 | // sellerAta: firstAta.address, 636 | // contractAta: contractAta.address, 637 | // tokenProgram: TOKEN_PROGRAM_ID, 638 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 639 | // systemProgram: anchor.web3.SystemProgram.programId, 640 | // }) 641 | // .signers([firstKp]) 642 | // .rpc(); 643 | 644 | // console.log("Your transaction signature for activating the contract:", tx); 645 | 646 | // contractAccount = await program.account.contract.fetch(contractAddress); 647 | 648 | // console.log("new contract after activating:", contractAccount); 649 | // } catch (error) { 650 | // // console.log("Error while activating contract!:", error); 651 | // assert.equal(error.error.errorCode.code, "InvalidActivator"); 652 | // assert.equal(error.error.errorCode.number, 6001); 653 | // assert.equal(error.error.errorMessage, "Invalid seller is trying to activate contract!"); 654 | // } 655 | // }); 656 | 657 | // it("2-[Success] Activate the contract!", async () => { 658 | // try { 659 | // let contractAccount = await program.account.contract.fetch(contractAddress); 660 | // console.log("new contract before activating:", contractAccount); 661 | 662 | // // Call the buy_tickets function 663 | // const tx = await program.methods 664 | // .activateContract(contractId) 665 | // .accounts({ 666 | // contract: contractAddress, 667 | // seller: secondKp.publicKey, 668 | // sellerAta: secondAta.address, 669 | // contractAta: contractAta.address, 670 | // tokenProgram: TOKEN_PROGRAM_ID, 671 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 672 | // systemProgram: anchor.web3.SystemProgram.programId, 673 | // }) 674 | // .signers([secondKp]) 675 | // .rpc({ skipPreflight: true }); 676 | 677 | // console.log("Your transaction signature for activating the contract:", tx); 678 | 679 | // contractAccount = await program.account.contract.fetch(contractAddress); 680 | 681 | // console.log("new contract after activating:", contractAccount); 682 | // } catch (error) { 683 | // console.log("Error while activating contract!:", error); 684 | // } 685 | // }); 686 | 687 | // // Testing dispute cases 688 | // it("3-[Success] Create a new contract!", async () => { 689 | // try { 690 | // // Create a new uuid to use as a new contract id 691 | // contractId = uuid().slice(0, 8); 692 | // console.log("new contractId:", contractId); 693 | 694 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 695 | // const dispute = new anchor.BN(0.5 * Math.pow(10, decimal)); // 0.5 BPT token; // 0.5 USDC 50 cent 696 | // const deadline = Math.floor(Date.now() / 1000) + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 697 | 698 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 699 | // [ 700 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 701 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 702 | // ], 703 | // program.programId 704 | // ); 705 | 706 | 707 | // contractAddress = contract; 708 | // contractBump = bump; 709 | // console.log("contractAddress", contractAddress); 710 | // console.log("contractBump", contractBump); 711 | 712 | // contractAta = await getOrCreateAssociatedTokenAccount( 713 | // program.provider.connection, 714 | // authKp, 715 | // payTokenMint, 716 | // contractAddress, 717 | // true 718 | // ); 719 | 720 | // // Call startContract function 721 | // const tx = await program.methods 722 | // .startContract( 723 | // contractId, 724 | // amount, 725 | // dispute, 726 | // deadline, 727 | 728 | // ) 729 | // .accounts({ 730 | // buyer: firstKp.publicKey, 731 | // contract, 732 | // seller: secondKp.publicKey, 733 | // buyerAta: firstAta.address, 734 | // contractAta: contractAta.address, 735 | // tokenProgram: TOKEN_PROGRAM_ID, 736 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 737 | // systemProgram: anchor.web3.SystemProgram.programId, 738 | // rent: SYSVAR_RENT_PUBKEY, 739 | // }) 740 | 741 | // .signers([firstKp]) 742 | // .rpc(); 743 | // // .rpc({ skipPreflight: true }); 744 | 745 | 746 | // console.log("Your transaction signature for creating a new contract", tx); 747 | 748 | // // Fetch the contract account and assert the values 749 | // const contractAccount = await program.account.contract.fetch(contract); 750 | 751 | // console.log("new contract account:", contractAccount); 752 | 753 | // } catch (error) { 754 | // console.log("Error while creating a new contract:", error); 755 | // } 756 | // }); 757 | 758 | // it("3-[Success] Activate the contract!", async () => { 759 | // try { 760 | // let contractAccount = await program.account.contract.fetch(contractAddress); 761 | // console.log("new contract before activating:", contractAccount); 762 | 763 | // // Call the buy_tickets function 764 | // const tx = await program.methods 765 | // .activateContract(contractId) 766 | // .accounts({ 767 | // contract: contractAddress, 768 | // seller: secondKp.publicKey, 769 | // sellerAta: secondAta.address, 770 | // contractAta: contractAta.address, 771 | // tokenProgram: TOKEN_PROGRAM_ID, 772 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 773 | // systemProgram: anchor.web3.SystemProgram.programId, 774 | // }) 775 | // .signers([secondKp]) 776 | // .rpc({ skipPreflight: true }); 777 | 778 | // console.log("Your transaction signature for activating the contract:", tx); 779 | 780 | // contractAccount = await program.account.contract.fetch(contractAddress); 781 | 782 | // console.log("new contract after activating:", contractAccount); 783 | // } catch (error) { 784 | // console.log("Error while activating contract!:", error); 785 | // } 786 | // }); 787 | 788 | // it("3-[Success-Dissatisfied(split)] Approve by buyer(client)!", async () => { 789 | // try { 790 | // let contractAccount = await program.account.contract.fetch(contractAddress); 791 | // console.log("new contract before approving on buyer!:", contractAccount); 792 | 793 | // const tx = await program.methods 794 | // .buyerApprove(contractId, true) 795 | // .accounts({ 796 | // contract: contractAddress, 797 | // buyer: firstKp.publicKey, 798 | // sellerAta: secondAta.address, 799 | // buyerAta: firstAta.address, 800 | // adminAta: authAta.address, 801 | // contractAta: contractAta.address, 802 | // tokenProgram: TOKEN_PROGRAM_ID, 803 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 804 | // systemProgram: anchor.web3.SystemProgram.programId, 805 | // }) 806 | // .signers([firstKp]) 807 | // .rpc({ skipPreflight: true }); 808 | 809 | // console.log("Your transaction signature for approving on buyer:", tx); 810 | 811 | // contractAccount = await program.account.contract.fetch(contractAddress); 812 | 813 | // console.log("new contract after approving on buyer!:", contractAccount); 814 | // } catch (error) { 815 | // console.log("Error while approving on buyer!:", error); 816 | // } 817 | // }); 818 | 819 | // // 1 - Agree with split 820 | // it("3-[Success-Agree with split] Approve by seller(freelancer)!", async () => { 821 | // try { 822 | // let contractAccount = await program.account.contract.fetch(contractAddress); 823 | // console.log("new contract before approving on seller!:", contractAccount); 824 | 825 | // const tx = await program.methods 826 | // .sellerApprove(contractId, true) 827 | // .accounts({ 828 | // contract: contractAddress, 829 | // seller: secondKp.publicKey, 830 | // sellerAta: secondAta.address, 831 | // buyerAta: firstAta.address, 832 | // adminAta: authAta.address, 833 | // contractAta: contractAta.address, 834 | // tokenProgram: TOKEN_PROGRAM_ID, 835 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 836 | // systemProgram: anchor.web3.SystemProgram.programId, 837 | // }) 838 | // .signers([secondKp]) 839 | // .rpc(); 840 | 841 | // console.log("Your transaction signature for approving on seller:", tx); 842 | 843 | // contractAccount = await program.account.contract.fetch(contractAddress); 844 | 845 | // console.log("new contract after approving on seller!:", contractAccount); 846 | // } catch (error) { 847 | // console.log("Error while approving on seller!:", error); 848 | // } 849 | // }); 850 | 851 | //## - Disagree with split so dispute 852 | // it("3-[Success-Disagree with split] Approve by seller(freelancer)!", async () => { 853 | // try { 854 | // let contractAccount = await program.account.contract.fetch(contractAddress); 855 | // console.log("new contract before approving on seller!:", contractAccount); 856 | 857 | // const tx = await program.methods 858 | // .sellerApprove(contractId, false) 859 | // .accounts({ 860 | // contract: contractAddress, 861 | // seller: secondKp.publicKey, 862 | // sellerAta: secondAta.address, 863 | // buyerAta: firstAta.address, 864 | // adminAta: authAta.address, 865 | // contractAta: contractAta.address, 866 | // tokenProgram: TOKEN_PROGRAM_ID, 867 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 868 | // systemProgram: anchor.web3.SystemProgram.programId, 869 | // }) 870 | // .signers([secondKp]) 871 | // .rpc(); 872 | 873 | // console.log("Your transaction signature for approving on seller:", tx); 874 | 875 | // contractAccount = await program.account.contract.fetch(contractAddress); 876 | 877 | // console.log("new contract after approving on seller!:", contractAccount); 878 | // } catch (error) { 879 | // console.log("Error while approving on seller!:", error); 880 | // } 881 | // }); 882 | // //## Invalid admin test 883 | // it("3-[Failure-invalid admin] Approve by admin!", async () => { 884 | // try { 885 | // let contractAccount = await program.account.contract.fetch(contractAddress); 886 | // console.log("new contract before approving on admin!:", contractAccount); 887 | 888 | // const tx = await program.methods 889 | // .adminApprove(contractId, 0) 890 | // .accounts({ 891 | // contract: contractAddress, 892 | // admin: firstKp.publicKey, 893 | // sellerAta: secondAta.address, 894 | // buyerAta: firstAta.address, 895 | // adminAta: authAta.address, 896 | // contractAta: contractAta.address, 897 | // tokenProgram: TOKEN_PROGRAM_ID, 898 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 899 | // systemProgram: anchor.web3.SystemProgram.programId, 900 | // }) 901 | // .signers([firstKp]) 902 | // .rpc(); 903 | 904 | // console.log("Your transaction signature for approving on admin:", tx); 905 | 906 | // contractAccount = await program.account.contract.fetch(contractAddress); 907 | 908 | // console.log("new contract after approving on admin!:", contractAccount); 909 | // } catch (error) { 910 | // // console.log("Error while approving on admin!:", error); 911 | // assert.equal(error.error.errorCode.code, "InvalidAdmin"); 912 | // assert.equal(error.error.errorCode.number, 6003); 913 | // assert.equal(error.error.errorMessage, "Invalid admin is trying to release funds!"); 914 | // } 915 | // }); 916 | //## 1.Agree with seller 917 | // it("3-[Success - Dispute-Agree with seller] Approve by admin!", async () => { 918 | // try { 919 | // let contractAccount = await program.account.contract.fetch(contractAddress); 920 | // console.log("new contract before approving on admin!:", contractAccount); 921 | 922 | // const tx = await program.methods 923 | // .adminApprove(contractId, 1) 924 | // .accounts({ 925 | // contract: contractAddress, 926 | // admin: authKp.publicKey, 927 | // sellerAta: secondAta.address, 928 | // buyerAta: firstAta.address, 929 | // adminAta: authAta.address, 930 | // contractAta: contractAta.address, 931 | // tokenProgram: TOKEN_PROGRAM_ID, 932 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 933 | // systemProgram: anchor.web3.SystemProgram.programId, 934 | // }) 935 | // .signers([authKp]) 936 | // .rpc(); 937 | 938 | // console.log("Your transaction signature for approving on admin:", tx); 939 | 940 | // contractAccount = await program.account.contract.fetch(contractAddress); 941 | 942 | // console.log("new contract after approving on admin!:", contractAccount); 943 | // } catch (error) { 944 | // console.log("Error while approving on admin!:", error); 945 | // } 946 | // }); 947 | 948 | // //## 2.Agree with buyer 949 | // it("3-[Success - Dispute-Agree with buyer] Approve by admin!", async () => { 950 | // try { 951 | // let contractAccount = await program.account.contract.fetch(contractAddress); 952 | // console.log("new contract before approving on admin!:", contractAccount); 953 | 954 | // const tx = await program.methods 955 | // .adminApprove(contractId, 2) 956 | // .accounts({ 957 | // contract: contractAddress, 958 | // admin: authKp.publicKey, 959 | // sellerAta: secondAta.address, 960 | // buyerAta: firstAta.address, 961 | // adminAta: authAta.address, 962 | // contractAta: contractAta.address, 963 | // tokenProgram: TOKEN_PROGRAM_ID, 964 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 965 | // systemProgram: anchor.web3.SystemProgram.programId, 966 | // }) 967 | // .signers([authKp]) 968 | // .rpc(); 969 | 970 | // console.log("Your transaction signature for approving on admin:", tx); 971 | 972 | // contractAccount = await program.account.contract.fetch(contractAddress); 973 | 974 | // console.log("new contract after approving on admin!:", contractAccount); 975 | // } catch (error) { 976 | // console.log("Error while approving on admin!:", error); 977 | // } 978 | // }); 979 | 980 | //## 3.Agree with split 981 | // it("3-[Success - Dispute-Agree with split] Approve by admin!", async () => { 982 | // try { 983 | // let contractAccount = await program.account.contract.fetch(contractAddress); 984 | // console.log("new contract before approving on admin!:", contractAccount); 985 | 986 | // const tx = await program.methods 987 | // .adminApprove(contractId, 3) 988 | // .accounts({ 989 | // contract: contractAddress, 990 | // admin: authKp.publicKey, 991 | // sellerAta: secondAta.address, 992 | // buyerAta: firstAta.address, 993 | // adminAta: authAta.address, 994 | // contractAta: contractAta.address, 995 | // tokenProgram: TOKEN_PROGRAM_ID, 996 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 997 | // systemProgram: anchor.web3.SystemProgram.programId, 998 | // }) 999 | // .signers([authKp]) 1000 | // .rpc(); 1001 | 1002 | // console.log("Your transaction signature for approving on admin:", tx); 1003 | 1004 | // contractAccount = await program.account.contract.fetch(contractAddress); 1005 | 1006 | // console.log("new contract after approving on admin!:", contractAccount); 1007 | // } catch (error) { 1008 | // console.log("Error while approving on admin!:", error); 1009 | // } 1010 | // }); 1011 | 1012 | 1013 | // it("4-[Success] Create a new contract on Seller!", async () => { 1014 | // try { 1015 | // // Create a new uuid to use as a new contract id 1016 | // contractId = uuid().slice(0, 8); 1017 | // console.log("new contractId:", contractId); 1018 | 1019 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 1020 | // const dispute = new anchor.BN(0.5 * Math.pow(10, decimal)); // 0.5 BPT token; // 0.5 USDC 50 cent 1021 | // const deadline = Math.floor(Date.now() / 1000) + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 1022 | 1023 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 1024 | // [ 1025 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 1026 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 1027 | // ], 1028 | // program.programId 1029 | // ); 1030 | 1031 | 1032 | // contractAddress = contract; 1033 | // contractBump = bump; 1034 | // console.log("contractAddress", contractAddress); 1035 | // console.log("contractBump", contractBump); 1036 | 1037 | // contractAta = await getOrCreateAssociatedTokenAccount( 1038 | // program.provider.connection, 1039 | // authKp, 1040 | // payTokenMint, 1041 | // contractAddress, 1042 | // true 1043 | // ); 1044 | 1045 | // // Call startContract function 1046 | // const tx = await program.methods 1047 | // .startContractOnSeller( 1048 | // contractId, 1049 | // amount, 1050 | // dispute, 1051 | // deadline, 1052 | 1053 | // ) 1054 | // .accounts({ 1055 | // buyer: firstKp.publicKey, 1056 | // contract, 1057 | // seller: secondKp.publicKey, 1058 | // buyerAta: firstAta.address, 1059 | // contractAta: contractAta.address, 1060 | // tokenProgram: TOKEN_PROGRAM_ID, 1061 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1062 | // systemProgram: anchor.web3.SystemProgram.programId, 1063 | // rent: SYSVAR_RENT_PUBKEY, 1064 | // }) 1065 | 1066 | // .signers([firstKp]) 1067 | // .rpc(); 1068 | // // .rpc({ skipPreflight: true }); 1069 | 1070 | 1071 | // console.log("Your transaction signature for creating a new contract", tx); 1072 | 1073 | // // Fetch the contract account and assert the values 1074 | // const contractAccount = await program.account.contract.fetch(contract); 1075 | 1076 | // console.log("new contract account:", contractAccount); 1077 | 1078 | // } catch (error) { 1079 | // console.log("Error while creating a new contract:", error); 1080 | // } 1081 | // }); 1082 | 1083 | // it("4-[Success] Accept the contract!", async () => { 1084 | // try { 1085 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1086 | // console.log("new contract before activating:", contractAccount); 1087 | 1088 | // // Call the buy_tickets function 1089 | // const tx = await program.methods 1090 | // .acceptContractOnBuyer(contractId) 1091 | // .accounts({ 1092 | // contract: contractAddress, 1093 | // seller: secondKp.publicKey, 1094 | // sellerAta: secondAta.address, 1095 | // contractAta: contractAta.address, 1096 | // tokenProgram: TOKEN_PROGRAM_ID, 1097 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1098 | // systemProgram: anchor.web3.SystemProgram.programId, 1099 | // }) 1100 | // .signers([secondKp]) 1101 | // .rpc({ skipPreflight: true }); 1102 | 1103 | // console.log("Your transaction signature for activating the contract:", tx); 1104 | 1105 | // contractAccount = await program.account.contract.fetch(contractAddress); 1106 | 1107 | // console.log("new contract after activating:", contractAccount); 1108 | // } catch (error) { 1109 | // console.log("Error while activating contract!:", error); 1110 | // } 1111 | // }); 1112 | 1113 | // it("4-[Success] Activate the contract!", async () => { 1114 | // try { 1115 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1116 | // console.log("new contract before activating:", contractAccount); 1117 | 1118 | // // Call the buy_tickets function 1119 | // const tx = await program.methods 1120 | // .activateContract(contractId, false) 1121 | // .accounts({ 1122 | // contract: contractAddress, 1123 | // seller: secondKp.publicKey, 1124 | // sellerAta: secondAta.address, 1125 | // contractAta: contractAta.address, 1126 | // tokenProgram: TOKEN_PROGRAM_ID, 1127 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1128 | // systemProgram: anchor.web3.SystemProgram.programId, 1129 | // }) 1130 | // .signers([secondKp]) 1131 | // .rpc({ skipPreflight: true }); 1132 | 1133 | // console.log("Your transaction signature for activating the contract:", tx); 1134 | 1135 | // contractAccount = await program.account.contract.fetch(contractAddress); 1136 | 1137 | // console.log("new contract after activating:", contractAccount); 1138 | // } catch (error) { 1139 | // console.log("Error while activating contract!:", error); 1140 | // } 1141 | // }); 1142 | 1143 | // it("4-[Failure-invalid buyer] Approve by buyer(client)!", async () => { 1144 | // try { 1145 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1146 | // console.log("new contract before approving on buyer!:", contractAccount); 1147 | 1148 | // const tx = await program.methods 1149 | // .buyerApprove(contractId, false) 1150 | // .accounts({ 1151 | // contract: contractAddress, 1152 | // buyer: secondKp.publicKey, 1153 | // sellerAta: secondAta.address, 1154 | // buyerAta: firstAta.address, 1155 | // adminAta: authAta.address, 1156 | // contractAta: contractAta.address, 1157 | // tokenProgram: TOKEN_PROGRAM_ID, 1158 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1159 | // systemProgram: anchor.web3.SystemProgram.programId, 1160 | // }) 1161 | // .signers([secondKp]) 1162 | // .rpc(); 1163 | 1164 | // console.log("Your transaction signature for approving on buyer:", tx); 1165 | 1166 | // contractAccount = await program.account.contract.fetch(contractAddress); 1167 | 1168 | // console.log("new contract after approving on buyer!:", contractAccount); 1169 | // } catch (error) { 1170 | // // console.log("Error while approving on buyer!:", error); 1171 | // assert.equal(error.error.errorCode.code, "InvalidBuyer"); 1172 | // assert.equal(error.error.errorCode.number, 6002); 1173 | // assert.equal(error.error.errorMessage, "Invalid buyer is trying to release funds!"); 1174 | // } 1175 | // }); 1176 | 1177 | // it("4-[Success-Satisfied(no split)] Approve by buyer(client)!", async () => { 1178 | // try { 1179 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1180 | // console.log("new contract before approving on buyer!:", contractAccount); 1181 | 1182 | // const tx = await program.methods 1183 | // .buyerApprove(contractId, false) 1184 | // .accounts({ 1185 | // contract: contractAddress, 1186 | // buyer: firstKp.publicKey, 1187 | // sellerAta: secondAta.address, 1188 | // buyerAta: firstAta.address, 1189 | // adminAta: authAta.address, 1190 | // contractAta: contractAta.address, 1191 | // tokenProgram: TOKEN_PROGRAM_ID, 1192 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1193 | // systemProgram: anchor.web3.SystemProgram.programId, 1194 | // }) 1195 | // .signers([firstKp]) 1196 | // .rpc({ skipPreflight: true }); 1197 | 1198 | // console.log("Your transaction signature for approving on buyer:", tx); 1199 | 1200 | // contractAccount = await program.account.contract.fetch(contractAddress); 1201 | 1202 | // console.log("new contract after approving on buyer!:", contractAccount); 1203 | // } catch (error) { 1204 | // console.log("Error while approving on buyer!:", error); 1205 | // } 1206 | // }); 1207 | 1208 | // it("1-[Failure-invalid seller] Approve by seller(freelancer)!", async () => { 1209 | // try { 1210 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1211 | // console.log("new contract before approving on seller!:", contractAccount); 1212 | 1213 | // const tx = await program.methods 1214 | // .sellerApprove(contractId, false) 1215 | // .accounts({ 1216 | // contract: contractAddress, 1217 | // seller: firstKp.publicKey, 1218 | // sellerAta: secondAta.address, 1219 | // buyerAta: firstAta.address, 1220 | // adminAta: authAta.address, 1221 | // contractAta: contractAta.address, 1222 | // tokenProgram: TOKEN_PROGRAM_ID, 1223 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1224 | // systemProgram: anchor.web3.SystemProgram.programId, 1225 | // }) 1226 | // .signers([firstKp]) 1227 | // .rpc(); 1228 | 1229 | // console.log("Your transaction signature for approving on seller:", tx); 1230 | 1231 | // contractAccount = await program.account.contract.fetch(contractAddress); 1232 | 1233 | // console.log("new contract after approving on seller!:", contractAccount); 1234 | // } catch (error) { 1235 | // // console.log("Error while approving on seller!:", error); 1236 | // assert.equal(error.error.errorCode.code, "InvalidSeller"); 1237 | // assert.equal(error.error.errorCode.number, 6000); 1238 | // assert.equal(error.error.errorMessage, "Invalid seller is trying to release funds!"); 1239 | // } 1240 | // }); 1241 | 1242 | // it("4-[Success-No split] Approve by seller(freelancer)!", async () => { 1243 | // try { 1244 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1245 | // console.log("new contract before approving on seller!:", contractAccount); 1246 | 1247 | // const tx = await program.methods 1248 | // .sellerApprove(contractId, false) 1249 | // .accounts({ 1250 | // contract: contractAddress, 1251 | // seller: secondKp.publicKey, 1252 | // sellerAta: secondAta.address, 1253 | // buyerAta: firstAta.address, 1254 | // adminAta: authAta.address, 1255 | // contractAta: contractAta.address, 1256 | // tokenProgram: TOKEN_PROGRAM_ID, 1257 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1258 | // systemProgram: anchor.web3.SystemProgram.programId, 1259 | // }) 1260 | // .signers([secondKp]) 1261 | // .rpc(); 1262 | 1263 | // console.log("Your transaction signature for approving on seller:", tx); 1264 | 1265 | // contractAccount = await program.account.contract.fetch(contractAddress); 1266 | 1267 | // console.log("new contract after approving on seller!:", contractAccount); 1268 | // } catch (error) { 1269 | // console.log("Error while approving on seller!:", error); 1270 | // } 1271 | // }); 1272 | 1273 | // it("4-[Failure] Approve by admin!", async () => { 1274 | // try { 1275 | // let contractAccount = await program.account.contract.fetch(contractAddress); 1276 | // console.log("new contract before approving on admin!:", contractAccount); 1277 | 1278 | // const tx = await program.methods 1279 | // .adminApprove(contractId, 0) 1280 | // .accounts({ 1281 | // contract: contractAddress, 1282 | // admin: authKp.publicKey, 1283 | // sellerAta: secondAta.address, 1284 | // buyerAta: firstAta.address, 1285 | // adminAta: authAta.address, 1286 | // contractAta: contractAta.address, 1287 | // tokenProgram: TOKEN_PROGRAM_ID, 1288 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1289 | // systemProgram: anchor.web3.SystemProgram.programId, 1290 | // }) 1291 | // .signers([authKp]) 1292 | // .rpc(); 1293 | 1294 | // console.log("Your transaction signature for approving on admin:", tx); 1295 | 1296 | // contractAccount = await program.account.contract.fetch(contractAddress); 1297 | 1298 | // console.log("new contract after approving on admin!:", contractAccount); 1299 | // } catch (error) { 1300 | // // console.log("Error while approving on admin!:", error); 1301 | // assert.equal(error.error.errorCode.code, "NotReadyYet"); 1302 | // assert.equal(error.error.errorCode.number, 6006); 1303 | // assert.equal(error.error.errorMessage, "Contract is not pending or disputed yet so admin can't approve now or already completed!"); 1304 | // } 1305 | // }); 1306 | 1307 | 1308 | // Hourly gig part 1309 | // it("Hourly-gig-part - start hourly gig contract on buyer!", async () => { 1310 | // try { 1311 | // // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1312 | // // console.log("new contract before starting hourly contract on buyer!:", contractAccount); 1313 | 1314 | // // Create a new uuid to use as a new contract id 1315 | // contractId = uuid().slice(0, 8); 1316 | // console.log("new contractId:", contractId); 1317 | 1318 | // const amount = new anchor.BN(10 * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 1319 | // const dispute = new anchor.BN(0.5 * Math.pow(10, decimal)); // 0.4 BPT token; // 0.4 USDC shoulb be 50 cent 1320 | // const deadline = Math.floor(Date.now() / 1000) + (10 * 24 * 60 * 60); // 10 days in seconds from Current timestamp 1321 | 1322 | // const [contract, bump] = anchor.web3.PublicKey.findProgramAddressSync( 1323 | // [ 1324 | // Buffer.from(anchor.utils.bytes.utf8.encode(CONTRACT_SEED)), 1325 | // Buffer.from(anchor.utils.bytes.utf8.encode(contractId)), 1326 | // ], 1327 | // program.programId 1328 | // ); 1329 | 1330 | 1331 | // contractAddress = contract; 1332 | // contractBump = bump; 1333 | // console.log("contractAddress", contractAddress); 1334 | // console.log("contractBump", contractBump); 1335 | 1336 | // contractAta = await getOrCreateAssociatedTokenAccount( 1337 | // program.provider.connection, 1338 | // authKp, 1339 | // payTokenMint, 1340 | // contractAddress, 1341 | // true 1342 | // ); 1343 | 1344 | // const hourly_rate = 30; 1345 | // const weekly_hours_limit = 40; 1346 | 1347 | // const tx = await program.methods 1348 | // .startHourlyContractOnBuyer(contractId, hourly_rate, weekly_hours_limit, dispute, deadline) 1349 | // .accounts({ 1350 | // contract: contractAddress, 1351 | // buyer: firstKp.publicKey, 1352 | // seller: secondKp.publicKey, 1353 | // buyerAta: firstAta.address, 1354 | // buyerReferral: null, 1355 | // contractAta: contractAta.address, 1356 | // payTokenMint, 1357 | // tokenProgram: TOKEN_PROGRAM_ID, 1358 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1359 | // systemProgram: anchor.web3.SystemProgram.programId, 1360 | // }) 1361 | // .signers([firstKp]) 1362 | // .rpc(); 1363 | 1364 | // console.log("Your transaction signature for starting hourly contract on buyer:", tx); 1365 | 1366 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1367 | 1368 | // console.log("new contract after starting hourly contract on buyer!:", contractAccount); 1369 | // } catch (error) { 1370 | // console.log("Error while starting hourly contract on buyer!:", error); 1371 | // } 1372 | // }); 1373 | 1374 | // it("Hourly-gig-part - activate hourly contract on seller!", async () => { 1375 | // try { 1376 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1377 | // console.log("new contract before activating hourly contract on seller!:", contractAccount); 1378 | 1379 | // const tx = await program.methods 1380 | // .activateHourlyContract(contractId, true) 1381 | // .accounts({ 1382 | // contract: contractAddress, 1383 | // seller: secondKp.publicKey, 1384 | // sellerAta: secondAta.address, 1385 | // sellerReferral: null, 1386 | // contractAta: contractAta.address, 1387 | // tokenProgram: TOKEN_PROGRAM_ID, 1388 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1389 | // systemProgram: anchor.web3.SystemProgram.programId, 1390 | // }) 1391 | // .signers([secondKp]) 1392 | // .rpc(); 1393 | 1394 | // console.log("Your transaction signature for activating hourly contract on seller:", tx); 1395 | 1396 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1397 | 1398 | // console.log("new contract after activating hourly contract on seller!:", contractAccount); 1399 | // } catch (error) { 1400 | // console.log("Error while activating hourly contract on seller!:", error); 1401 | // } 1402 | // }); 1403 | 1404 | // it("Hourly-gig-part - update worked hour!", async () => { 1405 | // try { 1406 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1407 | // console.log("new contract before updating worked hour on seller!:", contractAccount); 1408 | 1409 | // const tx = await program.methods 1410 | // .updateWorkedHour(contractId, 30) 1411 | // .accounts({ 1412 | // contract: contractAddress, 1413 | // seller: secondKp.publicKey, 1414 | // systemProgram: anchor.web3.SystemProgram.programId, 1415 | // }) 1416 | // .signers([secondKp]) 1417 | // .rpc(); 1418 | 1419 | // console.log("Your transaction signature for updating worked hour on seller:", tx); 1420 | 1421 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1422 | 1423 | // console.log("new contract after updating worked hour on seller!:", contractAccount); 1424 | // } catch (error) { 1425 | // console.log("Error while updating worked hour on seller!:", error); 1426 | // } 1427 | // }); 1428 | 1429 | // it("Hourly-gig-part - pay worked hours!", async () => { 1430 | // try { 1431 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1432 | // console.log("new contract before paying worked hours on seller!:", contractAccount); 1433 | 1434 | // const amount = new anchor.BN(contractAccount.weekWorkedHour * contractAccount.hourlyRate * Math.pow(10, decimal)); // 10 BPT token; // 10 USDC 1435 | // const tx = await program.methods 1436 | // .payWorkedHour(contractId, amount) 1437 | // .accounts({ 1438 | // contract: contractAddress, 1439 | // buyer: firstKp.publicKey, 1440 | // buyerAta: firstAta.address, 1441 | // contractAta: contractAta.address, 1442 | // payTokenMint, 1443 | // tokenProgram: TOKEN_PROGRAM_ID, 1444 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1445 | // systemProgram: anchor.web3.SystemProgram.programId, 1446 | // }) 1447 | // .signers([firstKp]) 1448 | // .rpc(); 1449 | 1450 | // console.log("Your transaction signature for paying worked hours on seller:", tx); 1451 | 1452 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1453 | 1454 | // console.log("new contract after paying worked hours on seller!:", contractAccount); 1455 | // } catch (error) { 1456 | // console.log("Error while paying worked hours on seller!:", error); 1457 | // } 1458 | // }); 1459 | 1460 | // it("Hourly-gig-part - seller approve hourly contract!", async () => { 1461 | // try { 1462 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1463 | // console.log("new contract before approving hourly contract on seller!:", contractAccount); 1464 | 1465 | // const tx = await program.methods 1466 | // .sellerApproveHourlyContract(contractId, false) 1467 | // .accounts({ 1468 | // contract: contractAddress, 1469 | // seller: secondKp.publicKey, 1470 | // sellerAta: secondAta.address, 1471 | // buyerAta: firstAta.address, 1472 | // adminAta: authAta.address, 1473 | // buyerReferral, 1474 | // sellerReferral, 1475 | // buyerReferralAta: buyerReferralAta.address, 1476 | // sellerReferralAta: sellerReferralAta.address, 1477 | // payTokenMint, 1478 | // contractAta: contractAta.address, 1479 | // tokenProgram: TOKEN_PROGRAM_ID, 1480 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 1481 | // systemProgram: anchor.web3.SystemProgram.programId, 1482 | // }) 1483 | // .signers([secondKp]) 1484 | // .rpc(); 1485 | 1486 | // console.log("Your transaction signature for approving hourly contract on seller:", tx); 1487 | 1488 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1489 | 1490 | // console.log("new contract after approving hourly contract on seller!:", contractAccount); 1491 | // } catch (error) { 1492 | // console.log("Error while approving hourly contract on seller!:", error); 1493 | // } 1494 | // }); 1495 | 1496 | // it("Hourly-gig-part - pause hourly contract!", async () => { 1497 | // try { 1498 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1499 | // console.log("new contract before pausing hourly contract:", contractAccount); 1500 | 1501 | // const tx = await program.methods 1502 | // .pauseHourlyContract(contractId) 1503 | // .accounts({ 1504 | // contract: contractAddress, 1505 | // buyer: firstKp.publicKey, 1506 | // systemProgram: anchor.web3.SystemProgram.programId, 1507 | // }) 1508 | // .signers([firstKp]) 1509 | // .rpc(); 1510 | 1511 | // console.log("Your transaction signature for pausing hourly contract:", tx); 1512 | 1513 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1514 | 1515 | // console.log("new contract after pausing hourly contract:", contractAccount); 1516 | // } catch (error) { 1517 | // console.log("Error while pausing hourly contract:", error); 1518 | // } 1519 | // }); 1520 | 1521 | // it("Hourly-gig-part - resume hourly contract!", async () => { 1522 | // try { 1523 | // let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1524 | // console.log("new contract before resuming hourly contract!:", contractAccount); 1525 | 1526 | // const tx = await program.methods 1527 | // .resumeHourlyContract(contractId) 1528 | // .accounts({ 1529 | // contract: contractAddress, 1530 | // buyer: firstKp.publicKey, 1531 | // systemProgram: anchor.web3.SystemProgram.programId, 1532 | // }) 1533 | // .signers([firstKp]) 1534 | // .rpc(); 1535 | 1536 | // console.log("Your transaction signature for resuming hourly contract:", tx); 1537 | 1538 | // contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1539 | 1540 | // console.log("new contract after resuming hourly contract!:", contractAccount); 1541 | // } catch (error) { 1542 | // console.log("Error while resuming hourly contract!:", error); 1543 | // } 1544 | // }); 1545 | 1546 | it("Hourly-gig-part - end hourly contract!", async () => { 1547 | try { 1548 | let contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1549 | console.log("new contract before ending hourly contract!:", contractAccount); 1550 | 1551 | const tx = await program.methods 1552 | .endHourlyContract(contractId) 1553 | .accounts({ 1554 | contract: contractAddress, 1555 | buyer: firstKp.publicKey, 1556 | systemProgram: anchor.web3.SystemProgram.programId, 1557 | }) 1558 | .signers([firstKp]) 1559 | .rpc(); 1560 | 1561 | console.log("Your transaction signature for ending hourly contract:", tx); 1562 | 1563 | contractAccount = await program.account.hourlyContract.fetch(contractAddress); 1564 | 1565 | console.log("new contract after ending hourly contract!:", contractAccount); 1566 | } catch (error) { 1567 | console.log("Error while ending hourly contract!:", error); 1568 | } 1569 | }); 1570 | }); 1571 | --------------------------------------------------------------------------------