diff --git a/.env.example b/.env.example
index d87ee1f..75629dc 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,8 @@
PROGRAM_ID=
+TOKEN_MINT=
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_NETWORK=devnet
FEE_PAYER_URL=https://localhost:8040/pay-fees
UNSTAKE_BASE_URL=http://localhost:3001/creators
+REWARDS_BASE_URL=https://localhost:3001/rewards
GET_ACS_URL=http://localhost:3001/get-acs
diff --git a/.env.production b/.env.production
index dbd670a..5c681d0 100644
--- a/.env.production
+++ b/.env.production
@@ -3,5 +3,6 @@ SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=6b3f6b84-a89d-485b-bea8-5
SOLANA_NETWORK="mainnet-beta"
FEE_PAYER_URL=https://go-api.accessprotocol.co/pay-fees
UNSTAKE_BASE_URL=https://hub.accessprotocol.co/creators
+REWARDS_BASE_URL=https://hub.accessprotocol.co/rewards
GET_ACS_URL=https://hub.accessprotocol.co/get-acs
TOKEN_MINT=5MAYDfq5yxtudAhtfyuMBuHZjgAbaS9tbEyEQYAhDS5y
diff --git a/.env.staging b/.env.staging
index 00d4ab0..01b2b6d 100644
--- a/.env.staging
+++ b/.env.staging
@@ -3,5 +3,6 @@ SOLANA_NETWORK=devnet
SOLANA_RPC_URL=https://api.devnet.solana.com
FEE_PAYER_URL=https://st-go-api.accessprotocol.co/pay-fees
UNSTAKE_BASE_URL=https://st-app.accessprotocol.co/creators
+REWARDS_BASE_URL=https://st-app.accessprotocol.co/rewards
GET_ACS_URL=https://st-app.accessprotocol.co/get-acs
TOKEN_MINT=5hGLVuE4wHW8mcHUJKEyoJYeg653bj8nZeXgUJrfMxFC
diff --git a/html-dev/index.html b/html-dev/index.html
index 7339e53..8724db8 100644
--- a/html-dev/index.html
+++ b/html-dev/index.html
@@ -25,8 +25,8 @@
The shown widget is for demonstration purpose only
_acs('init', {
element: document.getElementById('acs'),
debug: true,
- poolId: 'D9C7Yf5euSjpQ8Wo8XwJP7CWymm54oomeREkogiNf4yS',
- poolName: "Coingecko",
+ poolId: '7VLUA7nEQtppKk2n1Zb3EA7cXZ74SjvHH4bS5NBZSry',
+ poolName: "Crypto TImes",
});
document.querySelector("#acs").addEventListener("connected", (event) => {
console.log("Connected to the wallet with address: " + JSON.stringify(event.detail));
diff --git a/package.json b/package.json
index cef1ba2..eb0dd56 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
},
"license": "MIT",
"devDependencies": {
+ "@accessprotocol/js": "2.0.0-alpha.33",
"@babel/core": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
diff --git a/src/components/ProgressModal.tsx b/src/components/ProgressModal.tsx
index 66e7601..d1aa765 100644
--- a/src/components/ProgressModal.tsx
+++ b/src/components/ProgressModal.tsx
@@ -2,61 +2,36 @@ import { Fragment, h } from 'preact';
import { RouteLink } from '../layout/Router';
-import { ProgressStep } from './ProgressStep';
import { useContext } from 'preact/hooks';
import { ConfigContext } from '../AppContext';
import { clsxp } from '../libs/utils';
-
-const getStepState = (
- current: string,
- step: string,
- stepOrder: string[],
- doneStep: string
-) => {
- // find if the step is before, current, or after the current step;
- const currentStepIndex = stepOrder.indexOf(current);
- const stepIndex = stepOrder.indexOf(step);
- if (stepIndex < currentStepIndex || current === doneStep) {
- return 'complete';
- }
- if (stepIndex === currentStepIndex) {
- return 'current';
- }
- return '';
-};
+import Loading from './Loading';
const ProgressModal = ({
working,
- stepOrder,
doneStepName,
}: {
working: string;
- stepOrder: string[];
doneStepName: string;
}) => {
const { classPrefix } = useContext(ConfigContext);
return (
- Steps to complete
+ Sign a transaction
- We need you to sign these
- transactions to stake
+ { working === doneStepName ?
+ 'Transaction sent successfully.' :
+ 'We need you to sign a transaction to lock your funds.' }
-
- {stepOrder.map((step) => (
-
- ))}
-
+
+ { working !== doneStepName && }
+
{
- const { classPrefix } = useContext(ConfigContext);
- return (
-
-
-
-
-
-
- {name}
-
-
-
- );
-};
-
-const Current = ({ name }: { name: string }) => {
- const { classPrefix } = useContext(ConfigContext);
- return (
-
-
-
-
-
-
- {name}
-
-
- );
-};
-
-const Pending = ({ name }: { name: string }) => {
- const { classPrefix } = useContext(ConfigContext);
- return (
-
-
-
-
-
-
- {name}
-
-
-
- );
-};
-
-export const ProgressStep = ({
- name,
- status,
-}: {
- name: string;
- status: string;
-}) => {
- let component = null;
- switch (status) {
- case 'complete':
- component = ;
- break;
- case 'current':
- component = ;
- break;
- case 'pending':
- default:
- component = ;
- break;
- }
-
- return {component} ;
-};
diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx
index 5939ee3..e8d0459 100644
--- a/src/components/Tooltip.tsx
+++ b/src/components/Tooltip.tsx
@@ -4,19 +4,30 @@ import { ConfigContext } from '../AppContext';
import { clsxp } from '../libs/utils';
export const Tooltip = ({
- message,
- children,
-}: {
- message: string;
+ messages,
+ children,
+ }: {
+ messages: string[];
children: ComponentChildren;
}) => {
const { classPrefix } = useContext(ConfigContext);
+
return (
{children}
-
{message}
-
+
+
+ {messages
+ .filter((message) => message != null && message !== '')
+ .map((message, i) => (
+
{message}
+ ))}
+
+
+
);
diff --git a/src/hooks/useFeePayer.ts b/src/hooks/useFeePayer.ts
index de51b2b..ed1a867 100644
--- a/src/hooks/useFeePayer.ts
+++ b/src/hooks/useFeePayer.ts
@@ -1,29 +1,52 @@
-import { SendTransactionOptions } from '@solana/wallet-adapter-base';
-import { Connection, Transaction, TransactionSignature } from '@solana/web3.js';
+import { Connection, PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js';
import env from '../libs/env';
+import { useEffect, useState } from 'preact/hooks';
-export const useFeePayer = async (props: {
- sendTransaction: (
- transaction: Transaction,
- connection: Connection,
- options?: SendTransactionOptions
- ) => Promise;
-}) => {
- const sendTransaction = async (
- transaction: Transaction,
+export const useFeePayer = () => {
+ const [feePayerPubKey, setFeePayerPubKey] = useState(null);
+ useEffect(() => {
+ const fetchFeePayerPubKey = async () => {
+ const feePayer = await fetch(env.FEE_PAYER_URL, {
+ method: 'GET',
+ headers: new Headers({
+ Accept: 'application/vnd.github.cloak-preview',
+ }),
+ }).then((res) => res.text());
+ setFeePayerPubKey(new PublicKey(feePayer));
+ };
+ fetchFeePayerPubKey();
+ }, []);
+
+ const sendTxThroughGoApi = async (
connection: Connection,
- options?: SendTransactionOptions
+ instructions: TransactionInstruction[],
+ signTransaction: ((tx: Transaction) => Promise) | undefined,
) => {
+ if (!signTransaction) {
+ throw new Error('No sign transaction function provided.');
+ }
+ if (!feePayerPubKey) {
+ throw new Error('Fee payer public key not available.');
+ }
+
+ const blockhash = (await connection.getLatestBlockhash('max')).blockhash;
+
+ const tx = new Transaction();
+ tx.add(...instructions);
+ tx.feePayer = new PublicKey(feePayerPubKey);
+ tx.recentBlockhash = blockhash;
+ const signedTx = await signTransaction(tx);
+
const response = await fetch(env.FEE_PAYER_URL, {
method: 'POST',
headers: new Headers({
'Content-Type': 'text/plain',
}),
- body: transaction
+ body: JSON.stringify([signedTx
.serialize({
requireAllSignatures: false,
})
- .toString('hex'),
+ .toString('hex')]),
})
.then((res) => {
if (!res.ok) {
@@ -36,21 +59,11 @@ export const useFeePayer = async (props: {
throw new Error('Failed to sign transaction on backend');
}
- const tx = Transaction.from(Buffer.from(response, 'base64'));
- return tx.compileMessage().header.numRequiredSignatures === 1
- ? connection.sendRawTransaction(tx.serialize(), options)
- : props.sendTransaction(tx, connection, options);
+ return response.toString();
};
- const feePayerPubKey = await fetch(env.FEE_PAYER_URL, {
- method: 'GET',
- headers: new Headers({
- Accept: 'application/vnd.github.cloak-preview',
- }),
- }).then((res) => res.text());
-
return {
feePayerPubKey,
- sendTransaction,
+ sendTxThroughGoApi,
};
};
diff --git a/src/layout/Main.tsx b/src/layout/Main.tsx
index c13ab6b..70eba8b 100644
--- a/src/layout/Main.tsx
+++ b/src/layout/Main.tsx
@@ -19,7 +19,7 @@ import { WalletConnectButton } from '../components/wallet-adapter/ui/WalletConne
import { WalletModalButton } from '../components/wallet-adapter/ui/WalletModalButton';
import { useWallet } from '../components/wallet-adapter/useWallet';
import { ConfigContext } from '../AppContext';
-import { BondAccount, StakeAccount } from '../libs/ap/state';
+import { BondAccount, StakeAccount } from '@accessprotocol/js';
import env from '../libs/env';
import { useConnection } from '../components/wallet-adapter/useConnection';
import { getBondAccounts } from '../libs/program';
diff --git a/src/libs/ap/bindings.ts b/src/libs/ap/bindings.ts
deleted file mode 100644
index 5b98cdb..0000000
--- a/src/libs/ap/bindings.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import {
- claimRewardsInstruction,
- createStakeAccountInstruction,
- stakeInstruction,
- crankInstruction,
- claimBondRewardsInstruction,
-} from "./raw_instructions";
-import { Connection, PublicKey, SystemProgram } from "@solana/web3.js";
-import { CentralState, StakePool, StakeAccount, BondAccount } from "./state";
-import BN from "bn.js";
-import {
- TOKEN_PROGRAM_ID,
- getAssociatedTokenAddress,
- ASSOCIATED_TOKEN_PROGRAM_ID,
-} from "@solana/spl-token";
-import { getBondAccounts } from "../program";
-
-export const crank = async (
- stakePoolAccount: PublicKey,
- programId: PublicKey
-) => {
- const [centralKey] = await CentralState.getKey(programId);
- const ix = new crankInstruction().getInstruction(
- programId,
- stakePoolAccount,
- centralKey
- );
-
- return ix;
-};
-
-export const stake = async (
- connection: Connection,
- stakeAccount: PublicKey,
- sourceToken: PublicKey,
- amount: number,
- programId: PublicKey
-) => {
- const stake = await StakeAccount.retrieve(connection, stakeAccount);
- const stakePool = await StakePool.retrieve(connection, stake.stakePool);
- const [centralKey] = await CentralState.getKey(programId);
- const centralState = await CentralState.retrieve(connection, centralKey);
- const bondAccounts = await getBondAccounts(
- connection,
- stake.owner,
- programId
- );
- const bondAccountKey = bondAccounts.find(
- bond =>
- BondAccount.deserialize(bond.account.data).stakePool.toBase58() ===
- stake.stakePool.toBase58(),
- )?.pubkey;
-
- const feesAta = await getAssociatedTokenAddress(
- centralState.tokenMint,
- centralState.authority,
- true,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID
- );
-
- const ix = new stakeInstruction({
- amount: new BN(amount),
- }).getInstruction(
- programId,
- centralKey,
- stakeAccount,
- stake.stakePool,
- stake.owner,
- sourceToken,
- TOKEN_PROGRAM_ID,
- stakePool.vault,
- feesAta,
- bondAccountKey
- );
-
- return ix;
-};
-
-export const createStakeAccount = async (
- stakePool: PublicKey,
- owner: PublicKey,
- feePayer: PublicKey,
- programId: PublicKey
-) => {
- const [stakeAccount, nonce] = await StakeAccount.getKey(
- programId,
- owner,
- stakePool
- );
-
- const ix = new createStakeAccountInstruction({
- nonce,
- owner: owner.toBuffer(),
- }).getInstruction(
- programId,
- stakeAccount,
- SystemProgram.programId,
- stakePool,
- feePayer
- );
-
- return ix;
-};
-
-export const claimRewards = async (
- connection: Connection,
- stakeAccount: PublicKey,
- rewardsDestination: PublicKey,
- programId: PublicKey,
- allowZeroRewards: boolean,
- ownerMustSign = true
-) => {
- const stake = await StakeAccount.retrieve(connection, stakeAccount);
- const [centralKey] = await CentralState.getKey(programId);
- const centralState = await CentralState.retrieve(connection, centralKey);
-
- const ix = new claimRewardsInstruction({
- allowZeroRewards: Number(allowZeroRewards),
- }).getInstruction(
- programId,
- stake.stakePool,
- stakeAccount,
- stake.owner,
- rewardsDestination,
- centralKey,
- centralState.tokenMint,
- TOKEN_PROGRAM_ID
- );
-
- if (!ownerMustSign) {
- const idx = ix.keys.findIndex((e) => e.pubkey.equals(stake.owner));
- ix.keys[idx].isSigner = false;
- }
-
- return ix;
-};
-
-export const claimBondRewards = async (
- connection: Connection,
- bondAccount: PublicKey,
- rewardsDestination: PublicKey,
- programId: PublicKey,
- ownerMustSign = true
-) => {
- const [centralKey] = await CentralState.getKey(programId);
- const centralState = await CentralState.retrieve(connection, centralKey);
-
- const bond = await BondAccount.retrieve(connection, bondAccount);
-
- const ix = new claimBondRewardsInstruction().getInstruction(
- programId,
- bond.stakePool,
- bondAccount,
- bond.owner,
- rewardsDestination,
- centralKey,
- centralState.tokenMint,
- TOKEN_PROGRAM_ID
- );
-
- if (!ownerMustSign) {
- const idx = ix.keys.findIndex((e) => e.pubkey.equals(bond.owner));
- ix.keys[idx].isSigner = false;
- }
-
- return ix;
-};
diff --git a/src/libs/ap/raw_instructions.ts b/src/libs/ap/raw_instructions.ts
deleted file mode 100644
index 066e9f3..0000000
--- a/src/libs/ap/raw_instructions.ts
+++ /dev/null
@@ -1,349 +0,0 @@
-// This file is auto-generated. DO NOT EDIT
-import BN from "bn.js";
-import { Schema, serialize } from "borsh";
-import { PublicKey, TransactionInstruction } from "@solana/web3.js";
-
-interface AccountKey {
- pubkey: PublicKey;
- isSigner: boolean;
- isWritable: boolean;
-}
-export class crankInstruction {
- tag: number;
- static schema: Schema = new Map([
- [
- crankInstruction,
- {
- kind: "struct",
- fields: [["tag", "u8"]],
- },
- ],
- ]);
- constructor() {
- this.tag = 8;
- }
- serialize(): Uint8Array {
- return serialize(crankInstruction.schema, this);
- }
- getInstruction(
- programId: PublicKey,
- stakePool: PublicKey,
- centralState: PublicKey
- ): TransactionInstruction {
- const data = Buffer.from(this.serialize());
- let keys: AccountKey[] = [];
- keys.push({
- pubkey: stakePool,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: centralState,
- isSigner: false,
- isWritable: true,
- });
- return new TransactionInstruction({
- keys,
- programId,
- data,
- });
- }
-}
-export class createStakeAccountInstruction {
- tag: number;
- nonce: number;
- owner: Uint8Array;
- static schema: Schema = new Map([
- [
- createStakeAccountInstruction,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["nonce", "u8"],
- ["owner", [32]],
- ],
- },
- ],
- ]);
- constructor(obj: { nonce: number; owner: Uint8Array }) {
- this.tag = 3;
- this.nonce = obj.nonce;
- this.owner = obj.owner;
- }
- serialize(): Uint8Array {
- return serialize(createStakeAccountInstruction.schema, this);
- }
- getInstruction(
- programId: PublicKey,
- stakeAccount: PublicKey,
- systemProgram: PublicKey,
- stakePool: PublicKey,
- feePayer: PublicKey
- ): TransactionInstruction {
- const data = Buffer.from(this.serialize());
- let keys: AccountKey[] = [];
- keys.push({
- pubkey: stakeAccount,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: systemProgram,
- isSigner: false,
- isWritable: false,
- });
- keys.push({
- pubkey: stakePool,
- isSigner: false,
- isWritable: false,
- });
- keys.push({
- pubkey: feePayer,
- isSigner: true,
- isWritable: true,
- });
- return new TransactionInstruction({
- keys,
- programId,
- data,
- });
- }
-}
-export class stakeInstruction {
- tag: number;
- amount: BN;
- static schema: Schema = new Map([
- [
- stakeInstruction,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["amount", "u64"],
- ],
- },
- ],
- ]);
- constructor(obj: { amount: BN }) {
- this.tag = 4;
- this.amount = obj.amount;
- }
- serialize(): Uint8Array {
- return serialize(stakeInstruction.schema, this);
- }
- getInstruction(
- programId: PublicKey,
- centralStateAccount: PublicKey,
- stakeAccount: PublicKey,
- stakePool: PublicKey,
- owner: PublicKey,
- sourceToken: PublicKey,
- splTokenProgram: PublicKey,
- vault: PublicKey,
- feeAccount: PublicKey,
- bondAccount?: PublicKey
- ): TransactionInstruction {
- const data = Buffer.from(this.serialize());
- let keys: AccountKey[] = [];
- keys.push({
- pubkey: centralStateAccount,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: stakeAccount,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: stakePool,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: owner,
- isSigner: true,
- isWritable: false,
- });
- keys.push({
- pubkey: sourceToken,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: splTokenProgram,
- isSigner: false,
- isWritable: false,
- });
- keys.push({
- pubkey: vault,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: feeAccount,
- isSigner: false,
- isWritable: true,
- });
- if (bondAccount) {
- keys.push({
- pubkey: bondAccount,
- isSigner: false,
- isWritable: false,
- });
- }
- return new TransactionInstruction({
- keys,
- programId,
- data,
- });
- }
-}
-export class claimRewardsInstruction {
- tag: number;
- allowZeroRewards: number;
- static schema: Schema = new Map([
- [
- claimRewardsInstruction,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["allowZeroRewards", "u8"],
- ],
- },
- ],
- ]);
- constructor(obj: { allowZeroRewards: number }) {
- this.tag = 7;
- this.allowZeroRewards = obj.allowZeroRewards;
- }
- serialize(): Uint8Array {
- return serialize(claimRewardsInstruction.schema, this);
- }
- getInstruction(
- programId: PublicKey,
- stakePool: PublicKey,
- stakeAccount: PublicKey,
- owner: PublicKey,
- rewardsDestination: PublicKey,
- centralState: PublicKey,
- mint: PublicKey,
- splTokenProgram: PublicKey
- ): TransactionInstruction {
- const data = Buffer.from(this.serialize());
- let keys: AccountKey[] = [];
- keys.push({
- pubkey: stakePool,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: stakeAccount,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: owner,
- isSigner: true,
- isWritable: false,
- });
- keys.push({
- pubkey: rewardsDestination,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: centralState,
- isSigner: false,
- isWritable: false,
- });
- keys.push({
- pubkey: mint,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: splTokenProgram,
- isSigner: false,
- isWritable: false,
- });
- return new TransactionInstruction({
- keys,
- programId,
- data,
- });
- }
-}
-export class claimBondRewardsInstruction {
- tag: number;
- static schema: Schema = new Map([
- [
- claimBondRewardsInstruction,
- {
- kind: "struct",
- fields: [["tag", "u8"]],
- },
- ],
- ]);
- constructor() {
- this.tag = 16;
- }
- serialize(): Uint8Array {
- return serialize(claimBondRewardsInstruction.schema, this);
- }
- getInstruction(
- programId: PublicKey,
- stakePool: PublicKey,
- bondAccount: PublicKey,
- bondOwner: PublicKey,
- rewardsDestination: PublicKey,
- centralState: PublicKey,
- mint: PublicKey,
- splTokenProgram: PublicKey
- ): TransactionInstruction {
- const data = Buffer.from(this.serialize());
- let keys: AccountKey[] = [];
- keys.push({
- pubkey: stakePool,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: bondAccount,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: bondOwner,
- isSigner: true,
- isWritable: false,
- });
- keys.push({
- pubkey: rewardsDestination,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: centralState,
- isSigner: false,
- isWritable: false,
- });
- keys.push({
- pubkey: mint,
- isSigner: false,
- isWritable: true,
- });
- keys.push({
- pubkey: splTokenProgram,
- isSigner: false,
- isWritable: false,
- });
- return new TransactionInstruction({
- keys,
- programId,
- data,
- });
- }
-}
diff --git a/src/libs/ap/state.ts b/src/libs/ap/state.ts
deleted file mode 100644
index d2d820a..0000000
--- a/src/libs/ap/state.ts
+++ /dev/null
@@ -1,443 +0,0 @@
-import { deserialize, Schema } from "borsh";
-import BN from "bn.js";
-import { Connection, PublicKey } from "@solana/web3.js";
-import { u64 } from "./u64";
-
-/**
- * Lenght of the stake pool circular buffer used to store balances and inflation
- */
-const STAKE_BUFFER_LEN = 274; // 9 Months
-
-/**
- * Account tags (used for deserialization on-chain)
- */
-export enum Tag {
- Uninitialized = 0,
- StakePool = 1,
- InactiveStakePool = 2,
- StakeAccount = 3,
- // Bond accounts are inactive until the buyer transfered the funds
- InactiveBondAccount = 4,
- BondAccount = 5,
- CentralState = 6,
- Deleted = 7,
- FrozenStakePool = 8,
- FrozenStakeAccount = 9,
- FrozenBondAccount = 10,
-}
-
-/**
- * Stake pool state
- */
-export class RewardsTuple {
- poolReward: BN;
- stakersReward: BN;
-
- constructor(obj: {poolReward: BN; stakersReward: BN}) {
- this.poolReward = obj.poolReward;
- this.stakersReward = obj.stakersReward;
- }
-}
-
-/**
- * Stake pool state
- */
-export class StakePool {
- tag: Tag;
- nonce: number;
- currentDayIdx: number;
- _padding: Uint8Array;
- minimumStakeAmount: BN;
- totalStaked: BN;
- lastClaimedOffset: BN;
- stakersPart: BN;
- owner: PublicKey;
- vault: PublicKey;
-
- balances: RewardsTuple[];
-
- // rome-ignore lint/suspicious/noExplicitAny:
- static schema: Schema = new Map([
- [
- StakePool,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["nonce", "u8"],
- ["currentDayIdx", "u16"],
- ["_padding", [4]],
- ["minimumStakeAmount", "u64"],
- ["totalStaked", "u64"],
- ["lastClaimedOffset", "u64"],
- ["stakersPart", "u64"],
- ["owner", [32]],
- ["vault", [32]],
- ["balances", [RewardsTuple, STAKE_BUFFER_LEN]],
- ],
- },
- ],
- [
- RewardsTuple,
- {
- kind: "struct",
- fields: [
- ["poolReward", "u128"],
- ["stakersReward", "u128"],
- ],
- },
- ],
- ]);
-
- constructor(obj: {
- tag: number;
- nonce: number;
- currentDayIdx: number;
- _padding: Uint8Array;
- minimumStakeAmount: BN;
- totalStaked: BN;
- lastClaimedOffset: BN;
- stakersPart: BN;
- owner: Uint8Array;
- vault: Uint8Array;
-
- balances: RewardsTuple[];
- }) {
- this.tag = obj.tag as Tag;
- this.nonce = obj.nonce;
- this.currentDayIdx = obj.currentDayIdx;
- this._padding = obj._padding;
- this.minimumStakeAmount = obj.minimumStakeAmount;
- this.totalStaked = obj.totalStaked;
- this.lastClaimedOffset = obj.lastClaimedOffset.fromTwos(64);
- this.stakersPart = obj.stakersPart;
- this.owner = new PublicKey(obj.owner);
- this.vault = new PublicKey(obj.vault);
- this.balances = obj.balances;
- }
-
- static deserialize(data: Buffer) {
- return deserialize(this.schema, StakePool, data);
- }
-
- /**
- * This method can be used to retrieve the state of a stake pool
- * @param connection The Solana RPC connection
- * @param key The key of the stake pool
- * @returns
- */
- static async retrieve(connection: Connection, key: PublicKey) {
- const accountInfo = await connection.getAccountInfo(key);
- if (!accountInfo?.data) {
- throw new Error("Creator pool not found");
- }
- return this.deserialize(accountInfo.data);
- }
-
- /**
- * This method can be used to derive the stake pool key
- * @param programId The ACCESS program ID
- * @param owner The owner of the stake pool
- * @returns
- */
- static async getKey(programId: PublicKey, owner: PublicKey) {
- return await PublicKey.findProgramAddress(
- [Buffer.from("stake_pool"), owner.toBuffer()],
- programId
- );
- }
-}
-
-/**
- * Stake account state
- */
-export class StakeAccount {
- tag: Tag;
- owner: PublicKey;
- stakeAmount: BN;
- stakePool: PublicKey;
- lastClaimedOffset: BN;
- poolMinimumAtCreation: BN;
-
- // rome-ignore lint/suspicious/noExplicitAny:
- static schema: Schema = new Map([
- [
- StakeAccount,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["owner", [32]],
- ["stakeAmount", "u64"],
- ["stakePool", [32]],
- ["lastClaimedOffset", "u64"],
- ["poolMinimumAtCreation", "u64"],
- ],
- },
- ],
- ]);
-
- constructor(obj: {
- tag: number;
- owner: Uint8Array;
- stakeAmount: BN;
- stakePool: Uint8Array;
- lastClaimedOffset: BN;
- poolMinimumAtCreation: BN;
- }) {
- this.tag = obj.tag;
- this.owner = new PublicKey(obj.owner);
- this.stakeAmount = obj.stakeAmount;
- this.stakePool = new PublicKey(obj.stakePool);
- this.lastClaimedOffset = obj.lastClaimedOffset.fromTwos(64);
- this.poolMinimumAtCreation = obj.poolMinimumAtCreation;
- }
-
- static deserialize(data: Buffer) {
- return deserialize(this.schema, StakeAccount, data);
- }
-
- /**
- * This method can be used to retrieve the state of a stake account
- * @param connection The Solana RPC connection
- * @param key The stake account key
- * @returns
- */
- static async retrieve(connection: Connection, key: PublicKey) {
- const accountInfo = await connection.getAccountInfo(key);
- if (!accountInfo?.data) {
- throw new Error("Stake account not found");
- }
- return this.deserialize(accountInfo.data);
- }
-
- /**
- * This method can be used to derive the stake account key
- * @param programId The ACCESS program ID
- * @param owner The key of the stake account owner
- * @param stakePool The key of the stake pool
- * @returns
- */
- static async getKey(
- programId: PublicKey,
- owner: PublicKey,
- stakePool: PublicKey
- ) {
- return await PublicKey.findProgramAddress(
- [Buffer.from("stake_account"), owner.toBuffer(), stakePool.toBuffer()],
- programId
- );
- }
-}
-
-/**
- * The bond account state
- */
-export class BondAccount {
- tag: Tag;
- owner: PublicKey;
- totalAmountSold: BN;
- totalStaked: BN;
- totalQuoteAmount: BN;
- quoteMint: PublicKey;
- sellerTokenAccount: PublicKey;
- unlockStartDate: BN;
- unlockPeriod: BN;
- unlockAmount: BN;
- lastUnlockTime: BN;
- totalUnlockedAmount: BN;
- poolMinimumAtCreation: BN;
- stakePool: PublicKey;
- lastClaimedOffset: BN;
- sellers: PublicKey[];
-
- static schema: Schema = new Map([
- [
- BondAccount,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["owner", [32]],
- ["totalAmountSold", "u64"],
- ["totalStaked", "u64"],
- ["totalQuoteAmount", "u64"],
- ["quoteMint", [32]],
- ["sellerTokenAccount", [32]],
- ["unlockStartDate", "u64"],
- ["unlockPeriod", "u64"],
- ["unlockAmount", "u64"],
- ["lastUnlockTime", "u64"],
- ["totalUnlockedAmount", "u64"],
- ["poolMinimumAtCreation", "u64"],
- ["stakePool", [32]],
- ["lastClaimedOffset", "u64"],
- ["sellers", [[32]]],
- ],
- },
- ],
- ]);
-
- constructor(obj: {
- tag: number;
- owner: Uint8Array;
- totalAmountSold: BN;
- totalStaked: BN;
- totalQuoteAmount: BN;
- quoteMint: Uint8Array;
- sellerTokenAccount: Uint8Array;
- unlockStartDate: BN;
- unlockPeriod: BN;
- unlockAmount: BN;
- lastUnlockTime: BN;
- totalUnlockedAmount: BN;
- poolMinimumAtCreation: BN;
- stakePool: Uint8Array;
- lastClaimedOffset: BN;
- sellers: Uint8Array[];
- }) {
- this.tag = obj.tag as Tag;
- this.owner = new PublicKey(obj.owner);
- this.totalAmountSold = obj.totalAmountSold;
- this.totalStaked = obj.totalStaked;
- this.totalQuoteAmount = obj.totalQuoteAmount;
- this.quoteMint = new PublicKey(obj.quoteMint);
- this.sellerTokenAccount = new PublicKey(obj.sellerTokenAccount);
- this.unlockStartDate = obj.unlockStartDate;
- this.unlockPeriod = obj.unlockPeriod;
- this.unlockAmount = obj.unlockAmount;
- this.lastUnlockTime = obj.lastUnlockTime;
- this.totalUnlockedAmount = obj.totalUnlockedAmount;
- this.poolMinimumAtCreation = obj.poolMinimumAtCreation;
- this.stakePool = new PublicKey(obj.stakePool);
- this.lastClaimedOffset = obj.lastClaimedOffset;
- this.sellers = obj.sellers.map((e) => new PublicKey(e));
- }
-
- static deserialize(data: Buffer) {
- return deserialize(this.schema, BondAccount, data);
- }
-
- /**
- * This method can be used to retrieve the state of the bond account
- * @param connection The Solana RPC connection
- * @param key The key of the bond account
- * @returns
- */
- static async retrieve(connection: Connection, key: PublicKey) {
- const accountInfo = await connection.getAccountInfo(key);
- if (!accountInfo?.data) {
- throw new Error("Bond account not found");
- }
- return this.deserialize(accountInfo.data);
- }
-
- /**
- * This method can be used to derive the bond account key
- * @param programId The ACCESS program ID
- * @param owner The owner of the bond
- * @param totalAmountSold The total amount of ACCESS token sold in the bond
- * @returns
- */
- static async getKey(
- programId: PublicKey,
- owner: PublicKey,
- totalAmountSold: number
- ) {
- return await PublicKey.findProgramAddress(
- [
- Buffer.from("bond_account"),
- owner.toBuffer(),
- new u64(totalAmountSold).toBuffer(),
- ],
- programId
- );
- }
-}
-
-/**
- * The central state
- */
-export class CentralState {
- tag: Tag;
- signerNonce: number;
- dailyInflation: BN;
- tokenMint: PublicKey;
- authority: PublicKey;
- creationTime: BN;
- totalStaked: BN;
- totalStakedSnapshot: BN;
- lastSnapshotOffset: BN;
-
- static schema: Schema = new Map([
- [
- CentralState,
- {
- kind: "struct",
- fields: [
- ["tag", "u8"],
- ["signerNonce", "u8"],
- ["dailyInflation", "u64"],
- ["tokenMint", [32]],
- ["authority", [32]],
- ["creationTime", "u64"],
- ["totalStaked", "u64"],
- ["totalStakedSnapshot", "u64"],
- ["lastSnapshotOffset", "u64"],
- ],
- },
- ],
- ]);
-
- constructor(obj: {
- tag: number;
- signerNonce: number;
- dailyInflation: BN;
- tokenMint: Uint8Array;
- authority: Uint8Array;
- creationTime: BN;
- totalStaked: BN;
- totalStakedSnapshot: BN;
- lastSnapshotOffset: BN;
- }) {
- this.tag = obj.tag as Tag;
- this.signerNonce = obj.signerNonce;
- this.dailyInflation = obj.dailyInflation;
- this.tokenMint = new PublicKey(obj.tokenMint);
- this.authority = new PublicKey(obj.authority);
- this.creationTime = obj.creationTime.fromTwos(64);
- this.totalStaked = obj.totalStaked;
- this.totalStakedSnapshot = obj.totalStakedSnapshot.fromTwos(64);
- this.lastSnapshotOffset = obj.lastSnapshotOffset.fromTwos(64);
- }
-
- static deserialize(data: Buffer) {
- return deserialize(this.schema, CentralState, data);
- }
-
- /**
- * This method can be used to retrieve the state of the central state
- * @param connection The Solana RPC connection
- * @param key The key of the stake account
- * @returns
- */
- static async retrieve(connection: Connection, key: PublicKey) {
- const accountInfo = await connection.getAccountInfo(key);
- if (!accountInfo?.data) {
- throw new Error("Central state not found");
- }
- return this.deserialize(accountInfo.data);
- }
-
- /**
- * This method can be used to derive the central state key
- * @param programId The ACCESS program ID
- * @returns
- */
- static async getKey(programId: PublicKey) {
- return await PublicKey.findProgramAddress(
- [programId.toBuffer()],
- programId
- );
- }
-}
diff --git a/src/libs/ap/u64.ts b/src/libs/ap/u64.ts
deleted file mode 100644
index 522227e..0000000
--- a/src/libs/ap/u64.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import BN from 'bn.js';
-
-/**
- * 64-bit value
- */
-export class u64 extends BN {
- constructor(
- input: string | number | BN | Buffer | Uint8Array | number[],
- number?: number
- ) {
- super(input, number ?? 10);
- }
-
- /**
- * Convert to Buffer representation
- */
- toBuffer(): Buffer {
- const a = super.toArray().reverse();
- const b = Buffer.from(a);
- if (b.length === 8) {
- return b;
- }
- if (b.length > 8) {
- throw new Error('u64 too large');
- }
-
- const zeroPad = Buffer.alloc(8);
- b.copy(zeroPad);
- return zeroPad;
- }
-
- /**
- * Construct a u64 from Buffer representation
- */
- static fromBuffer(buffer: Buffer): u64 {
- if (buffer.length !== 8) {
- throw new Error(`Invalid buffer length: ${buffer.length}`);
- }
- return new u64(
- [...buffer]
- .reverse()
- .map((i) => `00${i.toString(16)}`.slice(-2))
- .join(''),
- 16
- );
- }
-}
diff --git a/src/libs/env.ts b/src/libs/env.ts
index 2fdf62d..f3f81d3 100644
--- a/src/libs/env.ts
+++ b/src/libs/env.ts
@@ -7,6 +7,7 @@ const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL;
const SOLANA_NETWORK = process.env.SOLANA_NETWORK;
const FEE_PAYER_URL = process.env.FEE_PAYER_URL;
const UNSTAKE_BASE_URL = process.env.UNSTAKE_BASE_URL;
+const REWARDS_BASE_URL = process.env.REWARDS_BASE_URL;
const GET_ACS_URL = process.env.GET_ACS_URL;
if (!SOLANA_RPC_URL) {
@@ -33,6 +34,10 @@ if (!UNSTAKE_BASE_URL) {
throw new Error('UNSTAKE_BASE_URL must be set!');
}
+if (!REWARDS_BASE_URL) {
+ throw new Error('REWARDS_BASE_URL must be set!');
+}
+
if (!GET_ACS_URL) {
throw new Error('GET_ACS_URL must be set!');
}
@@ -44,6 +49,7 @@ interface Config {
TOKEN_MINT: PublicKey;
FEE_PAYER_URL: string;
UNSTAKE_BASE_URL: string;
+ REWARDS_BASE_URL: string;
GET_ACS_URL: string;
}
@@ -54,6 +60,7 @@ const config: Config = {
TOKEN_MINT: new PublicKey(TOKEN_MINT),
FEE_PAYER_URL,
UNSTAKE_BASE_URL,
+ REWARDS_BASE_URL,
GET_ACS_URL,
};
diff --git a/src/libs/program.ts b/src/libs/program.ts
index 1cc17ce..f757efa 100644
--- a/src/libs/program.ts
+++ b/src/libs/program.ts
@@ -1,4 +1,4 @@
-import { CentralState, StakePool } from './ap/state';
+import { CentralStateV2, StakePool } from '@accessprotocol/js';
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
getAssociatedTokenAddress,
@@ -18,6 +18,7 @@ import BN from 'bn.js';
* This function can be used to find all stake accounts of a user
* @param connection The Solana RPC connection
* @param owner The owner of the stake accounts to retrieve
+ * @param programId The program ID
* @returns
*/
export const getStakeAccounts = async (
@@ -112,8 +113,8 @@ export const getUserACSBalance = async (
publicKey: PublicKey,
programId: PublicKey
): Promise => {
- const [centralKey] = await CentralState.getKey(programId);
- const centralState = await CentralState.retrieve(connection, centralKey);
+ const [centralKey] = await CentralStateV2.getKey(programId);
+ const centralState = await CentralStateV2.retrieve(connection, centralKey);
const userAta: PublicKey = await getAssociatedTokenAddress(
centralState.tokenMint,
publicKey,
diff --git a/src/libs/transactions.ts b/src/libs/transactions.ts
deleted file mode 100644
index f93153a..0000000
--- a/src/libs/transactions.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { SendTransactionOptions } from '@solana/wallet-adapter-base';
-import {
- Connection,
- PublicKey,
- TransactionInstruction,
- Transaction,
- ConfirmOptions,
-} from '@solana/web3.js';
-
-export const sendTx = async (
- connection: Connection,
- feePayer: PublicKey,
- instructions: TransactionInstruction[],
- sendTransaction: (
- tx: Transaction,
- connection: Connection,
- sendOptions?: SendTransactionOptions
- ) => Promise,
- sendOptions?: SendTransactionOptions,
- options?: ConfirmOptions
-) => {
- const latestBlockHash = await connection.getLatestBlockhash();
-
- const transaction = new Transaction({ ...latestBlockHash }).add(
- ...instructions
- );
- transaction.feePayer = feePayer;
-
- const signature = await sendTransaction(transaction, connection, sendOptions);
-
- // TODO: Return back after this resolves: https://github.com/solana-labs/solana/pull/28290
- // const status =
- // transaction.recentBlockhash != null &&
- // transaction.lastValidBlockHeight != null
- // ? (
- // await connection.confirmTransaction(
- // {
- // signature: signature,
- // ...latestBlockHash,
- // },
- // options && options.commitment
- // )
- // ).value
- // : (
- // await connection.confirmTransaction(
- // signature,
- // options && options.commitment
- // )
- // ).value;
-
- // if (status.err) {
- // throw new Error(
- // `Transaction ${signature} failed (${JSON.stringify(status)})`
- // );
- // }
-
- console.log('Signature: ', signature);
- return signature;
-};
diff --git a/src/main.css b/src/main.css
index 8ca707e..1d9c42e 100644
--- a/src/main.css
+++ b/src/main.css
@@ -131,7 +131,11 @@ button.acs__wallet_adapter_button_trigger {
/* >> components/Tooltip.tsx */
.acs__tooltip_root {
- @apply relative flex flex-row items-center justify-center;
+ @apply relative flex flex-row items-center justify-center px-2;
+}
+
+.acs__tooltip_root:hover .acs__tooltip_wrapper {
+ @apply cursor-pointer flex visible;
}
.acs__tooltip_wrapper {
@@ -206,8 +210,8 @@ button.acs__wallet_adapter_button_trigger {
@apply flex flex-col justify-start my-4;
}
-.acs__process_modal_steps_list {
- @apply space-y-4 list-none mb-10;
+.acs__process_modal_steps_load {
+ @apply mx-auto pb-20;
}
.acs__process_modal_button {
@@ -227,7 +231,7 @@ button.acs__wallet_adapter_button_trigger {
/* >> components/NumberInputWithSlider.tsx */
.acs__number_input_with_slider_root {
- @apply relative my-6;
+ @apply relative mt-6 mb-2;
}
.acs__number_input_with_slider_slider {
@@ -342,52 +346,27 @@ button.acs__wallet_adapter_button_trigger {
}
.acs__claim_button {
- @apply w-full rounded-full cursor-pointer no-underline font-bold py-4 mb-4 block text-xl text-center bg-indigo-500 text-stone-700 border-0;
+ @apply w-full rounded-full cursor-pointer no-underline font-bold py-4 block text-xl text-center bg-indigo-500 text-stone-700;
}
.acs__claim_button:hover {
@apply bg-indigo-300 text-stone-800;
}
-.acs__claim_button:hover:disabled,
-.acs__claim_button:disabled {
- @apply bg-stone-600 cursor-not-allowed;
-}
-
.acs__claim_title {
- @apply my-8 mt-16 text-white text-2xl text-center;
-}
-
-.acs__claim_title_error {
- @apply mt-8 text-red-500 text-2xl text-center;
+ @apply text-white text-2xl text-center;
}
.acs__claim_subtitle {
- @apply text-center text-stone-400;
-}
-
-.acs__claim_subtitle_error {
- @apply text-red-500 text-center;
+ @apply text-center text-stone-400 mb-14;
}
.acs__claim_claim_amount {
- @apply mb-40 text-4xl text-center text-green-400;
-}
-
-.acs__claim_loader {
- @apply flex justify-center content-center mb-56;
+ @apply text-4xl text-center text-green-400;
}
-.acs__claim_steps {
- @apply flex flex-col justify-start my-4;
-}
-
-.acs__claim_steps_list {
- @apply space-y-4 list-none mb-10;
-}
-
-.acs__claim_invalid {
- @apply bg-red-400;
+.acs__claim_footnote {
+ @apply flex justify-center text-sm text-indigo-500 mt-2 mb-2;
}
/* >> routes/Stake.tsx */
@@ -404,18 +383,35 @@ button.acs__wallet_adapter_button_trigger {
@apply w-full rounded-full cursor-pointer no-underline font-bold py-4 block text-xl text-center bg-indigo-500 text-stone-700 border-0;
}
+.acs__forever_stake_button {
+ @apply w-full rounded-full cursor-pointer no-underline font-bold py-4 block text-xl text-center bg-indigo-500 text-stone-700 border-0;
+}
+
+.acs__stake_checkbox {
+ @apply w-full flex gap-3 pb-3 items-center justify-center text-red-300
+}
+
.acs__stake_button:hover {
@apply bg-indigo-300 text-stone-800;
}
+.acs__forever_stake_button:hover {
+ @apply bg-indigo-300 text-stone-800;
+}
+
+
.acs__stake_button:disabled,
.acs__stake_button:disabled:hover,
-.acs__stake_button_disabled {
+.acs__stake_button_disabled,
+.acs__forever_stake_button:disabled,
+.acs__forever_stake_button:disabled:hover,
+.acs__forever_stake_button_disabled
+{
@apply bg-stone-600 cursor-not-allowed;
}
.acs__stake_title {
- @apply my-8 mt-16 text-white text-3xl text-center;
+ @apply my-8 mt-7 text-white text-3xl text-center;
}
.acs__stake_title_error {
diff --git a/src/routes/Actions.tsx b/src/routes/Actions.tsx
index ad46d95..ac175de 100644
--- a/src/routes/Actions.tsx
+++ b/src/routes/Actions.tsx
@@ -1,22 +1,11 @@
import { h } from 'preact';
-import {
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
-} from 'preact/hooks';
+import { useCallback, useContext, useEffect, useMemo, useState, } from 'preact/hooks';
import { PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
-import {
- calculateRewardForStaker,
- getBondAccounts,
- getStakeAccounts,
- getUserACSBalance,
-} from '../libs/program';
+import { calculateRewardForStaker, getBondAccounts, getStakeAccounts, getUserACSBalance, } from '../libs/program';
import { ConfigContext } from '../AppContext';
-import { BondAccount, StakeAccount, StakePool } from '../libs/ap/state';
+import { BondAccount, BondV2Account, getBondV2Accounts, StakeAccount, StakePool } from '@accessprotocol/js';
import { clsxp, formatPenyACSCurrency } from '../libs/utils';
import { RouteLink } from '../layout/Router';
import { Header } from '../components/Header';
@@ -32,9 +21,8 @@ export const Actions = () => {
const [stakedAccount, setStakedAccount] = useState<
StakeAccount | null | undefined
>(undefined);
- const [bondAccount, setBondAccount] = useState<
- BondAccount | null | undefined
- >(undefined);
+ const [bondAccounts, setBondAccounts] = useState([]);
+ const [bondV2Accounts, setBondV2Accounts] = useState([]);
const [stakePool, setStakePool] = useState(undefined);
useEffect(() => {
@@ -89,25 +77,33 @@ export const Actions = () => {
return;
}
(async () => {
- const bondAccounts = await getBondAccounts(
+ const bAccounts = await getBondAccounts(
connection,
publicKey,
env.PROGRAM_ID
);
- if (bondAccounts != null && bondAccounts.length > 0) {
- const bAccount = bondAccounts.find((st) => {
- const sa = BondAccount.deserialize(st.account.data);
- return sa.stakePool.toBase58() === poolId;
- });
- if (bAccount) {
- const ba = BondAccount.deserialize(bAccount.account.data);
- setBondAccount(ba);
- } else {
- setBondAccount(null);
- }
- } else {
- setBondAccount(null);
- }
+ setBondAccounts(
+ bAccounts.map((bAccount: any) => BondAccount.deserialize(bAccount.account.data))
+ .filter((bAccount: BondAccount) => bAccount.stakePool.toBase58() === poolId)
+ );
+ })();
+ }, [publicKey, connection, poolId]);
+
+ useEffect(() => {
+ if (!(publicKey && poolId && connection)) {
+ return;
+ }
+ (async () => {
+ const bV2Accounts = await getBondV2Accounts(
+ connection,
+ publicKey,
+ env.PROGRAM_ID
+ );
+
+ setBondV2Accounts(
+ bV2Accounts.map((bAccount: any) => BondV2Account.deserialize(bAccount.account.data))
+ .filter((bAccount: BondV2Account) => bAccount.pool.toBase58() === poolId)
+ );
})();
}, [publicKey, connection, poolId]);
@@ -115,29 +111,42 @@ export const Actions = () => {
if (!(stakedAccount && stakePool)) {
return null;
}
- const reward = calculateRewardForStaker(
+ return calculateRewardForStaker(
stakePool.currentDayIdx - stakedAccount.lastClaimedOffset.toNumber(),
stakePool,
stakedAccount.stakeAmount as BN
);
- return reward;
}, [stakedAccount, stakePool]);
const claimableBondAmount = useMemo(() => {
- if (!(bondAccount && stakePool)) {
+ if (bondAccounts.length === 0 || !stakePool) {
return null;
}
- const reward = calculateRewardForStaker(
- stakePool.currentDayIdx - bondAccount.lastClaimedOffset.toNumber(),
- stakePool,
- bondAccount.totalStaked as BN
- );
- return reward;
- }, [bondAccount, stakePool]);
+
+ return bondAccounts.reduce((acc, ba) =>
+ acc + calculateRewardForStaker(
+ stakePool.currentDayIdx - ba.lastClaimedOffset.toNumber(),
+ stakePool,
+ ba.totalStaked as BN
+ ), 0);
+ }, [bondAccounts, stakePool]);
+
+ const claimableBondV2Amount = useMemo(() => {
+ if (bondV2Accounts.length === 0 || !stakePool) {
+ return null;
+ }
+
+ return bondV2Accounts.reduce((acc, ba) =>
+ acc + calculateRewardForStaker(
+ stakePool.currentDayIdx - ba.lastClaimedOffset.toNumber(),
+ stakePool,
+ ba.amount as BN
+ ), 0);
+ }, [bondV2Accounts, stakePool]);
const claimableAmount = useMemo(() => {
- return (claimableBondAmount ?? 0) + (claimableStakeAmount ?? 0);
- }, [claimableBondAmount, claimableStakeAmount]);
+ return (claimableBondAmount ?? 0) + (claimableStakeAmount ?? 0) + (claimableBondV2Amount ?? 0);
+ }, [claimableBondAmount, claimableStakeAmount, claimableBondV2Amount]);
const disconnectHandler = useCallback(async () => {
try {
@@ -147,6 +156,27 @@ export const Actions = () => {
}
}, [disconnect]);
+ const hasUnlockableBonds = useMemo(() => {
+ if (!stakePool || !bondAccounts) {
+ return [];
+ }
+ return bondAccounts.find((ba) => {
+ return ba.unlockStartDate.toNumber() <= Date.now() / 1000;
+ });
+ }, [stakePool, bondAccounts]);
+
+ const hasUnlockableBondsV2 = useMemo(() => {
+ if (!stakePool || !bondV2Accounts) {
+ return [];
+ }
+ return bondV2Accounts.find((ba) => {
+ if (!ba.unlockTimestamp) {
+ return false;
+ }
+ return ba.unlockTimestamp.toNumber() <= Date.now() / 1000;
+ });
+ }, [stakePool, bondV2Accounts]);
+
return (
{connected && disconnecting && (
@@ -187,15 +217,18 @@ export const Actions = () => {
className={clsxp(
classPrefix,
'actions_staked_amount',
- (stakedAccount === undefined || bondAccount === undefined) &&
- 'actions_blink'
+ (stakedAccount === undefined || bondAccounts === undefined) &&
+ 'actions_blink'
)}
>
- {formatPenyACSCurrency(
- (stakedAccount?.stakeAmount.toNumber() ?? 0) +
- (bondAccount?.totalStaked.toNumber() ?? 0)
- )}{' '}
- ACS locked
+
+ {formatPenyACSCurrency(
+ (stakedAccount?.stakeAmount.toNumber() ?? 0) +
+ (bondAccounts?.reduce((acc, ba) => acc + ba.totalStaked.toNumber(), 0) ?? 0) +
+ (bondV2Accounts?.reduce((acc, ba) => acc + ba.amount.toNumber(), 0) ?? 0)
+ )}{' '}
+ ACS locked
+
{
className={clsxp(
classPrefix,
'actions_balance',
- (stakedAccount === undefined || bondAccount === undefined) &&
- 'actions_blink'
+ (stakedAccount === undefined || bondAccounts === undefined) &&
+ 'actions_blink'
)}
>
- {formatPenyACSCurrency(claimableAmount ?? 0)} ACS claimable
+ {formatPenyACSCurrency(claimableAmount ?? 0)} ACS rewards
@@ -225,7 +258,7 @@ export const Actions = () => {
>
Lock
- {stakedAccount && stakedAccount.stakeAmount.toNumber() > 0 ? (
+ {(stakedAccount && stakedAccount.stakeAmount.toNumber() > 0) || hasUnlockableBonds || hasUnlockableBondsV2 ? (
{
'actions_button_disabled'
)}
>
- Claim
+ Claim rewards
)}
diff --git a/src/routes/Claim.tsx b/src/routes/Claim.tsx
index 6e37ee0..2050c71 100644
--- a/src/routes/Claim.tsx
+++ b/src/routes/Claim.tsx
@@ -1,75 +1,38 @@
-import { Fragment, h } from 'preact';
-import BN from 'bn.js';
-import {
- BondAccount,
- CentralState,
- StakeAccount,
- StakePool,
-} from '../libs/ap/state';
-import { claimBondRewards, claimRewards } from '../libs/ap/bindings';
-import {
- TOKEN_PROGRAM_ID,
- getAssociatedTokenAddress,
- ASSOCIATED_TOKEN_PROGRAM_ID,
-} from '@solana/spl-token';
-import { PublicKey } from '@solana/web3.js';
-import { useContext, useEffect, useMemo, useState } from 'preact/hooks';
+import { h } from 'preact';
import { Header } from '../components/Header';
import { RouteLink } from '../layout/Router';
+import { useContext, useEffect, useMemo, useState } from 'preact/hooks';
import { ConfigContext } from '../AppContext';
-import { useConnection } from '../components/wallet-adapter/useConnection';
-import { useWallet } from '../components/wallet-adapter/useWallet';
-import {
- calculateRewardForStaker,
- getBondAccounts,
- getStakeAccounts,
-} from '../libs/program';
-import { sendTx } from '../libs/transactions';
-import Loading from '../components/Loading';
-import { clsxp, formatPenyACSCurrency } from '../libs/utils';
import env from '../libs/env';
-import { ProgressModal } from '../components/ProgressModal';
-import { useFeePayer } from '../hooks/useFeePayer';
-import { WalletAdapterProps } from '@solana/wallet-adapter-base';
-
-const CLAIM_BOND_REWARDS_STEP = 'Claim airdrop rewards';
-const CLAIM_STAKE_REWARDS_STEP = 'Claim stake rewards';
-const DONE_STEP = 'Done';
-const IDLE_STEP = 'Idle';
-
-interface FeePaymentData {
- feePayerPubKey: string;
- sendTransaction: WalletAdapterProps['sendTransaction'];
-}
+import { clsxp, formatPenyACSCurrency } from '../libs/utils';
+import { BondAccount, BondV2Account, getBondV2Accounts, StakeAccount, StakePool } from '@accessprotocol/js';
+import { calculateRewardForStaker, getBondAccounts, getStakeAccounts } from '../libs/program';
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+import { useWallet } from '../components/wallet-adapter/useWallet';
+import { useConnection } from '../components/wallet-adapter/useConnection';
export const Claim = () => {
- const { poolId, poolName, element, classPrefix } = useContext(ConfigContext);
+ const { poolId, classPrefix } = useContext(ConfigContext);
const { connection } = useConnection();
- const { publicKey, sendTransaction: sendTransactionWithFeesUnpaid } =
- useWallet();
+ const { publicKey } = useWallet();
- const [feePaymentState, setFeePayer] = useState();
+ const [stakedAccount, setStakedAccount] = useState<
+ StakeAccount | null | undefined
+ >(undefined);
+ const [bondAccounts, setBondAccounts] = useState([]);
+ const [bondV2Accounts, setBondV2Accounts] = useState([]);
+ const [stakePool, setStakePool] = useState(undefined);
useEffect(() => {
+ if (!(poolId && connection)) {
+ return;
+ }
(async () => {
- const { feePayerPubKey: pubkey, sendTransaction } = await useFeePayer({
- sendTransaction: sendTransactionWithFeesUnpaid,
- });
- setFeePayer({ feePayerPubKey: pubkey, sendTransaction });
+ setStakePool(await StakePool.retrieve(connection, new PublicKey(poolId)));
})();
- }, [publicKey]);
-
- const [working, setWorking] = useState(IDLE_STEP);
- const [stakedAccount, setStakedAccount] = useState(null);
- const [bondAccount, setBondAccount] = useState<
- BondAccount | null | undefined
- >(undefined);
- const [stakedPool, setStakedPool] = useState(null);
- const [stakeModalOpen, setStakeModal] = useState(false);
- const [error, setError] = useState(null);
-
- const openStakeModal = () => setStakeModal(true);
+ }, [poolId, connection]);
useEffect(() => {
if (!(publicKey && poolId && connection)) {
@@ -92,237 +55,128 @@ export const Claim = () => {
} else {
setStakedAccount(null);
}
- return;
+ } else {
+ setStakedAccount(null);
}
- setStakedAccount(null);
})();
}, [publicKey, connection, poolId]);
useEffect(() => {
- if (!(publicKey && poolId)) {
+ if (!(publicKey && poolId && connection)) {
return;
}
(async () => {
- const bondAccounts = await getBondAccounts(
+ const bAccounts = await getBondAccounts(
connection,
publicKey,
env.PROGRAM_ID
);
- if (bondAccounts != null && bondAccounts.length > 0) {
- const bAccount = bondAccounts.find((st) => {
- const sa = BondAccount.deserialize(st.account.data);
- return sa.stakePool.toBase58() === poolId;
- });
- if (bAccount) {
- const ba = BondAccount.deserialize(bAccount.account.data);
- setBondAccount(ba);
- } else {
- setBondAccount(null);
- }
+ if (bAccounts != null && bAccounts.length > 0) {
+ const bas = bAccounts.filter((st) => {
+ const ba = BondAccount.deserialize(st.account.data);
+ return ba.stakePool.toBase58() === poolId;
+ }).map((bAccount) => BondAccount.deserialize(bAccount.account.data));
+ setBondAccounts(bas);
} else {
- setBondAccount(null);
+ setBondAccounts([]);
}
})();
}, [publicKey, connection, poolId]);
useEffect(() => {
- if (!stakedAccount?.owner) {
+ if (!(publicKey && poolId && connection)) {
return;
}
(async () => {
- const sp = await StakePool.retrieve(connection, stakedAccount.stakePool);
- setStakedPool(sp);
+ const bV2Accounts = await getBondV2Accounts(
+ connection,
+ publicKey,
+ env.PROGRAM_ID
+ );
+
+ setBondV2Accounts(
+ bV2Accounts.map((bAccount: any) => BondV2Account.deserialize(bAccount.account.data))
+ .filter((bAccount: BondV2Account) => bAccount.pool.toBase58() === poolId)
+ );
})();
- }, [stakedAccount?.owner]);
+ }, [publicKey, connection, poolId]);
const claimableStakeAmount = useMemo(() => {
- if (!(stakedAccount && stakedPool)) {
+ if (!(stakedAccount && stakePool)) {
return null;
}
return calculateRewardForStaker(
- stakedPool.currentDayIdx - stakedAccount.lastClaimedOffset.toNumber(),
- stakedPool,
+ stakePool.currentDayIdx - stakedAccount.lastClaimedOffset.toNumber(),
+ stakePool,
stakedAccount.stakeAmount as BN
);
- }, [stakedAccount, stakedPool]);
+ }, [stakedAccount, stakePool]);
const claimableBondAmount = useMemo(() => {
- if (!(bondAccount && stakedPool)) {
+ if (bondAccounts.length === 0 || !stakePool) {
return null;
}
- return calculateRewardForStaker(
- stakedPool.currentDayIdx - bondAccount.lastClaimedOffset.toNumber(),
- stakedPool,
- bondAccount.totalStaked as BN
- );
- }, [bondAccount, stakedPool]);
-
- const claimableAmount = useMemo(() => {
- return (claimableBondAmount ?? 0) + (claimableStakeAmount ?? 0);
- }, [claimableBondAmount, claimableStakeAmount]);
- const handle = async () => {
- if (
- !(
- publicKey &&
- poolId &&
- connection &&
- stakedAccount &&
- bondAccount &&
- stakedPool
- )
- ) {
- return;
- }
+ return bondAccounts.reduce((acc, ba) =>
+ acc + calculateRewardForStaker(
+ stakePool.currentDayIdx - ba.lastClaimedOffset.toNumber(),
+ stakePool,
+ ba.totalStaked as BN
+ ), 0);
+ }, [bondAccounts, stakePool]);
- let feePayer = publicKey;
- let sendTransaction = sendTransactionWithFeesUnpaid;
- if (publicKey != null && feePaymentState != null) {
- feePayer = new PublicKey(feePaymentState.feePayerPubKey);
- sendTransaction = feePaymentState.sendTransaction;
+ const claimableBondV2Amount = useMemo(() => {
+ if (bondV2Accounts.length === 0 || !stakePool) {
+ return null;
}
- try {
- openStakeModal();
-
- const [centralKey] = await CentralState.getKey(env.PROGRAM_ID);
- const centralState = await CentralState.retrieve(connection, centralKey);
-
- const [stakeKey] = await StakeAccount.getKey(
- env.PROGRAM_ID,
- publicKey,
- new PublicKey(poolId)
- );
-
- const [bondKey] = await BondAccount.getKey(
- env.PROGRAM_ID,
- publicKey,
- bondAccount.totalAmountSold.toNumber()
- );
-
- const stakerAta = await getAssociatedTokenAddress(
- centralState.tokenMint,
- publicKey,
- true,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID
- );
-
- if (claimableStakeAmount && claimableStakeAmount > 0) {
- setWorking(CLAIM_STAKE_REWARDS_STEP);
- const ix = await claimRewards(
- connection,
- stakeKey,
- stakerAta,
- env.PROGRAM_ID,
- true
- );
-
- await sendTx(connection, feePayer, [ix], sendTransaction, {
- skipPreflight: true,
- });
- }
-
- if (claimableBondAmount && claimableBondAmount > 0) {
- setWorking(CLAIM_BOND_REWARDS_STEP);
- const ix = await claimBondRewards(
- connection,
- bondKey,
- stakerAta,
- env.PROGRAM_ID,
- true
- );
-
- await sendTx(connection, feePayer, [ix], sendTransaction, {
- skipPreflight: true,
- });
- }
+ return bondV2Accounts.reduce((acc, ba) =>
+ acc + calculateRewardForStaker(
+ stakePool.currentDayIdx - ba.lastClaimedOffset.toNumber(),
+ stakePool,
+ ba.amount as BN
+ ), 0);
+ }, [bondV2Accounts, stakePool]);
- const claimEvent = new CustomEvent('claim', {
- detail: {
- address: publicKey.toBase58(),
- locked: claimableStakeAmount,
- airdrop: claimableBondAmount,
- },
- bubbles: true,
- cancelable: true,
- composed: false, // if you want to listen on parent turn this on
- });
- element?.dispatchEvent(claimEvent);
-
- setWorking(DONE_STEP);
- } catch (err) {
- if (err instanceof Error) {
- console.error(err);
- setError(err.message);
- }
- } finally {
- setWorking(DONE_STEP);
- }
- };
+ const claimableAmount = useMemo(() => {
+ return (claimableBondAmount ?? 0) + (claimableStakeAmount ?? 0) + (claimableBondV2Amount ?? 0);
+ }, [claimableBondAmount, claimableStakeAmount, claimableBondV2Amount]);
return (
- {stakeModalOpen && error && (
-
-
- Error occured:
-
-
- {error}
-
-
- Close
-
-
- )}
- {stakeModalOpen && !error && (
-
- )}
- {!stakeModalOpen && (
-
-
-
- {stakedAccount?.stakeAmount && stakedPool ? (
-
-
- Claim on '{poolName}'
-
-
- {formatPenyACSCurrency(claimableAmount)} ACS
-
-
-
-
- Claim
-
-
-
- ) : (
-
-
-
- )}
-
- )}
+
+
+
Claim ACS Rewards
+
+
+ {formatPenyACSCurrency(claimableAmount)} ACS
+
+
+
+ ACS reward claim is currently only possible in the Access app.
+
+
+
);
};
diff --git a/src/routes/Stake.tsx b/src/routes/Stake.tsx
index 471e5ad..e4cbacf 100644
--- a/src/routes/Stake.tsx
+++ b/src/routes/Stake.tsx
@@ -1,24 +1,13 @@
import { Fragment, h } from 'preact';
import { Info } from 'phosphor-react';
import {
- BondAccount,
- CentralState,
+ BondV2Account,
+ CentralStateV2,
+ fullLock,
+ getBondV2Accounts,
StakeAccount,
StakePool,
-} from '../libs/ap/state';
-import {
- claimRewards,
- crank,
- createStakeAccount,
- stake,
-} from '../libs/ap/bindings';
-import {
- TOKEN_PROGRAM_ID,
- getAssociatedTokenAddress,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- createAssociatedTokenAccountInstruction,
- createTransferInstruction,
-} from '@solana/spl-token';
+} from '@accessprotocol/js';
import { PublicKey } from '@solana/web3.js';
import { useContext, useEffect, useMemo, useState } from 'preact/hooks';
@@ -27,104 +16,70 @@ import { RouteLink } from '../layout/Router';
import { ConfigContext } from '../AppContext';
import { useConnection } from '../components/wallet-adapter/useConnection';
import { useWallet } from '../components/wallet-adapter/useWallet';
-import {
- calculateRewardForStaker,
- getBondAccounts,
- getStakeAccounts,
- getUserACSBalance,
-} from '../libs/program';
+import { getStakeAccounts, getUserACSBalance, } from '../libs/program';
import { Tooltip } from '../components/Tooltip';
import { NumberInputWithSlider } from '../components/NumberInputWithSlider';
-import { sendTx } from '../libs/transactions';
import Loading from '../components/Loading';
import { ProgressModal } from '../components/ProgressModal';
-import { clsxp, formatACSCurrency, sleep } from '../libs/utils';
-import { useFeePayer } from '../hooks/useFeePayer';
-import { WalletAdapterProps } from '@solana/wallet-adapter-base';
+import { clsxp, formatACSCurrency } from '../libs/utils';
import env from '../libs/env';
-import BN from 'bn.js';
-
-interface FeePaymentData {
- feePayerPubKey: string;
- sendTransaction: WalletAdapterProps['sendTransaction'];
-}
+import { useFeePayer } from '../hooks/useFeePayer';
-const CRANK_STEP = 'Crank';
-const CREATE_STAKING_ACCOUNT_STEP = 'Create locking account';
-const CLAIM_REWARDS_STEP = 'Claim rewards';
-const STAKE_STEP = 'Lock ACS';
const DONE_STEP = 'Done';
const IDLE_STEP = 'Idle';
+const ACCOUNT_CREATION_ACS_PRICE = 50;
+
+const calculateFees = (amount: number,
+ feeBasisPoints: number,
+ forever: boolean,
+ stakeAccount?: StakeAccount | null,
+ bondV2Accounts?: BondV2Account[],
+) => {
+ let accountCreationFee = ACCOUNT_CREATION_ACS_PRICE;
+ if ((!forever && stakeAccount) || (forever && bondV2Accounts && bondV2Accounts.length > 0)) {
+ accountCreationFee = 0;
+ }
+ let protocolFee = amount * (feeBasisPoints / 10000);
+ if (forever) {
+ protocolFee = 0;
+ }
+ return protocolFee + accountCreationFee;
+};
+
export const Stake = () => {
const { poolId, poolName, element, classPrefix } = useContext(ConfigContext);
const { connection } = useConnection();
- const { publicKey, sendTransaction: sendTransactionWithFeesUnpaid } =
+ const { publicKey, signTransaction } =
useWallet();
-
- const [feePaymentState, setFeePayer] = useState();
-
- useEffect(() => {
- (async () => {
- const { feePayerPubKey: pubkey, sendTransaction } = await useFeePayer({
- sendTransaction: sendTransactionWithFeesUnpaid,
- });
- setFeePayer({ feePayerPubKey: pubkey, sendTransaction });
- })();
- }, [publicKey]);
-
+ const { feePayerPubKey, sendTxThroughGoApi } = useFeePayer();
const [working, setWorking] = useState(IDLE_STEP);
const [balance, setBalance] = useState(undefined);
- const [solBalance, setSolBalance] = useState(0);
- const [stakedAccount, setStakedAccount] = useState<
+ const [forever, setForever] = useState(false);
+ const [stakeAccount, setStakeAccount] = useState<
StakeAccount | undefined | null
>(undefined);
- const [bondAccount, setBondAccount] = useState<
- BondAccount | null | undefined
- >(undefined);
- const [stakedPool, setStakedPool] = useState(null);
+ const [bondV2Accounts, setBondV2Accounts] = useState([]);
+ const [stakedPool, setStakePool] = useState(null);
const [stakeAmount, setStakeAmount] = useState(0);
+ const [feeBasisPoints, setFeeBasisPoints] = useState(0);
const [stakeModalOpen, setStakeModal] = useState(false);
const [error, setError] = useState(null);
const openStakeModal = () => setStakeModal(true);
- const feePercentage = 2;
- const feePercentageFraction = feePercentage / 100;
-
- const claimableStakeAmount = useMemo(() => {
- if (!(stakedAccount && stakedPool)) {
- return null;
- }
- return calculateRewardForStaker(
- stakedPool.currentDayIdx - stakedAccount.lastClaimedOffset.toNumber(),
- stakedPool,
- stakedAccount.stakeAmount as BN
- );
- }, [stakedAccount, stakedPool]);
-
- useEffect(() => {
- if (!(publicKey && connection)) {
- return;
- }
- (async () => {
- const b = await connection.getBalance(publicKey);
- setSolBalance(b / 10 ** 9);
- })();
- }, [publicKey, connection, setSolBalance]);
-
+ // set stake pool
useEffect(() => {
- if (!(publicKey && connection)) {
+ if (!poolId) {
return;
}
(async () => {
- const b = await getUserACSBalance(connection, publicKey, env.PROGRAM_ID);
- const acsBalance = (b?.toNumber() || 0) / 10 ** 6;
- setBalance(acsBalance);
- setStakeAmount(acsBalance / (1 + feePercentageFraction));
+ const sp = await StakePool.retrieve(connection, new PublicKey(poolId));
+ setStakePool(sp);
})();
- }, [publicKey, connection, getUserACSBalance]);
+ }, [poolId]);
+ // set stake account
useEffect(() => {
if (!(publicKey && poolId && connection)) {
return;
@@ -142,73 +97,72 @@ export const Stake = () => {
});
if (sAccount) {
const sa = StakeAccount.deserialize(sAccount.account.data);
- setStakedAccount(sa);
+ setStakeAccount(sa);
} else {
- setStakedAccount(null);
+ setStakeAccount(null);
}
return;
}
- setStakedAccount(null);
+ setStakeAccount(null);
})();
- }, [publicKey, connection, poolId, setStakedAccount]);
+ }, [publicKey, connection, poolId, setStakeAccount]);
+ // set bond account
useEffect(() => {
- if (!(publicKey && poolId)) {
+ if (!(publicKey && poolId && connection)) {
return;
}
(async () => {
- const bondAccounts = await getBondAccounts(
+ const bV2Accounts = await getBondV2Accounts(
connection,
publicKey,
env.PROGRAM_ID
);
- if (bondAccounts != null && bondAccounts.length > 0) {
- const bAccount = bondAccounts.find((st) => {
- const sa = BondAccount.deserialize(st.account.data);
- return sa.stakePool.toBase58() === poolId;
- });
- if (bAccount) {
- const ba = BondAccount.deserialize(bAccount.account.data);
- setBondAccount(ba);
- } else {
- setBondAccount(null);
- }
- } else {
- setBondAccount(null);
- }
+
+ setBondV2Accounts(
+ bV2Accounts.map((bAccount: any) => BondV2Account.deserialize(bAccount.account.data))
+ .filter((bAccount: BondV2Account) => bAccount.pool.toBase58() === poolId)
+ );
})();
- }, [publicKey, connection, poolId, setBondAccount]);
+ }, [publicKey, connection, poolId]);
+ // set fee basis points from the central state
useEffect(() => {
- if (!poolId) {
+ if (!(publicKey && connection)) {
return;
}
(async () => {
- const sp = await StakePool.retrieve(connection, new PublicKey(poolId));
- setStakedPool(sp);
+ const cs = await CentralStateV2.retrieve(
+ connection,
+ CentralStateV2.getKey(env.PROGRAM_ID)[0],
+ );
+ setFeeBasisPoints(cs.feeBasisPoints);
})();
- }, [poolId]);
-
- const ACCOUNT_CREATION_ACS_PRICE = 30 * 10 ** 6;
+ }, [connection, setFeeBasisPoints]);
- const fee = useMemo(() => {
- return Number(stakeAmount) * feePercentageFraction + 30;
- }, [stakeAmount, feePercentageFraction]);
-
- const handle = async () => {
- let feePayer = publicKey;
- let sendTransaction = sendTransactionWithFeesUnpaid;
- if (
- insufficientSolBalance &&
- publicKey != null &&
- feePaymentState != null
- ) {
- feePayer = new PublicKey(feePaymentState.feePayerPubKey);
- sendTransaction = feePaymentState.sendTransaction;
+ // set ACS balance and initial stake amount
+ useEffect(() => {
+ if (!(publicKey && connection)) {
+ return;
}
+ (async () => {
+ const b = await getUserACSBalance(connection, publicKey, env.PROGRAM_ID);
+ const acsBalance = (b?.toNumber() || 0) / 10 ** 6;
+ setBalance(acsBalance);
+ const max = Math.floor(acsBalance - calculateFees(
+ acsBalance,
+ feeBasisPoints,
+ false,
+ stakeAccount,
+ undefined,
+ ));
+ setStakeAmount(max);
+ })();
+ }, [publicKey, connection, stakeAccount, getUserACSBalance]);
+ const handle = async () => {
if (
- !(publicKey && poolId && connection && feePayer && balance && stakedPool)
+ !(publicKey && poolId && connection && feePayerPubKey && balance && stakedPool)
) {
return;
}
@@ -216,164 +170,22 @@ export const Stake = () => {
try {
openStakeModal();
- const [centralKey] = await CentralState.getKey(env.PROGRAM_ID);
- const centralState = await CentralState.retrieve(connection, centralKey);
- const txs = [];
-
- let hasCranked = false;
- if (
- centralState.lastSnapshotOffset.toNumber() > stakedPool.currentDayIdx ||
- centralState.creationTime.toNumber() +
- 86400 * (stakedPool.currentDayIdx + 1) <
- Date.now() / 1000
- ) {
- setWorking(CRANK_STEP);
- const crankTx = await crank(new PublicKey(poolId), env.PROGRAM_ID);
- await sendTx(connection, feePayer, [crankTx], sendTransaction, {
- skipPreflight: true,
- });
- hasCranked = true;
- }
-
- // Check if stake account exists
- const [stakeKey] = await StakeAccount.getKey(
- env.PROGRAM_ID,
- publicKey,
- new PublicKey(poolId)
- );
-
- let stakeAccount;
- try {
- stakeAccount = await StakeAccount.retrieve(connection, stakeKey);
- } catch {
- setWorking(CREATE_STAKING_ACCOUNT_STEP);
- const statxs = [];
- if (feePayer.toBase58() !== publicKey.toBase58()) {
- const from = publicKey;
- const to = feePayer;
- const sourceATA = await getAssociatedTokenAddress(
- env.TOKEN_MINT,
- from,
- false,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- );
- const destinationATA = await getAssociatedTokenAddress(
- env.TOKEN_MINT,
- to,
- false,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- );
- const transferIx = createTransferInstruction(
- sourceATA,
- destinationATA,
- from,
- ACCOUNT_CREATION_ACS_PRICE,
- [],
- TOKEN_PROGRAM_ID,
- );
- statxs.push(transferIx);
- }
- const ixAccount = await createStakeAccount(
- new PublicKey(poolId),
- publicKey,
- feePayer,
- env.PROGRAM_ID
- );
- statxs.push(ixAccount);
- await sendTx(connection, feePayer, statxs, sendTransaction, {
- skipPreflight: true,
- });
-
- try {
- stakeAccount = await StakeAccount.retrieve(connection, stakeKey);
- } catch (e) {
- console.warn('Stake account not ready yet.');
- }
- let attempts = 0;
- while (stakeAccount == null && attempts < 20) {
- // eslint-disable-next-line no-await-in-loop
- await sleep(2000);
- console.log('Sleeping...');
- // eslint-disable-next-line no-await-in-loop
- try {
- stakeAccount = await StakeAccount.retrieve(connection, stakeKey);
- } catch (e) {
- console.warn('Stake account not ready yet attempt: ', attempts);
- }
- attempts += 1;
- }
- }
-
- if (stakeAccount == null) {
- throw new Error('Stake account not created on time... please try again to lock tokens.');
- }
-
- const stakerAta = await getAssociatedTokenAddress(
- centralState.tokenMint,
- publicKey,
- true,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID
- );
-
- const stakerAtaAccount = await connection.getAccountInfo(stakerAta);
- if (stakerAtaAccount == null) {
- const ataix = createAssociatedTokenAccountInstruction(
- feePayer,
- stakerAta,
- publicKey,
- centralState.tokenMint,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID
- );
- txs.push(ataix);
- }
-
- if (
- stakeAccount.stakeAmount.toNumber() > 0 &&
- (stakeAccount.lastClaimedOffset.toNumber() < stakedPool.currentDayIdx ||
- hasCranked)
- ) {
- setWorking(CLAIM_REWARDS_STEP);
- const ix = await claimRewards(
- connection,
- stakeKey,
- stakerAta,
- env.PROGRAM_ID,
- true
- );
-
- await sendTx(connection, feePayer, [ix], sendTransaction, {
- skipPreflight: true,
- });
-
- const claimEvent = new CustomEvent('claim', {
- detail: {
- address: publicKey.toBase58(),
- locked: claimableStakeAmount,
- },
- bubbles: true,
- cancelable: true,
- composed: false, // if you want to listen on parent turn this on
- });
- element?.dispatchEvent(claimEvent);
- }
-
- setWorking(STAKE_STEP);
- const ixStake = await stake(
+ const ixs = await fullLock(
connection,
- stakeKey,
- stakerAta,
- Number(stakeAmount) * 10 ** 6,
- env.PROGRAM_ID
+ publicKey,
+ new PublicKey(poolId),
+ feePayerPubKey,
+ Number(stakeAmount),
+ Date.now() / 1000,
+ ACCOUNT_CREATION_ACS_PRICE * 1e6,
+ env.PROGRAM_ID,
+ undefined,
+ stakedPool,
+ forever ? 0 : -1,
);
- txs.push(ixStake);
- await sendTx(connection, feePayer, txs, sendTransaction, {
- skipPreflight: true,
- });
+ const sx = await sendTxThroughGoApi(connection, ixs, signTransaction);
+ console.log('SIGNATURE:', sx);
const lockedEvent = new CustomEvent('lock', {
detail: {
@@ -397,48 +209,66 @@ export const Stake = () => {
}
};
- const minPoolStakeAmount = useMemo(() => {
- return (stakedPool?.minimumStakeAmount.toNumber() ?? 0) / 10 ** 6;
- }, [stakedPool?.minimumStakeAmount]);
-
const minStakeAmount = useMemo(() => {
- const stakedAmount = Number(stakedAccount?.stakeAmount ?? 0) / 10 ** 6;
- const airdropAmount = Number(bondAccount?.totalStaked ?? 0) / 10 ** 6;
- return Math.max(minPoolStakeAmount - stakedAmount - airdropAmount, 1);
+ const stakedAmount = Number(stakeAccount?.stakeAmount ?? 0) / 10 ** 6;
+ const minPoolStakeAmount = (stakedPool?.minimumStakeAmount.toNumber() ?? 0) / 10 ** 6;
+ const bondV2Amount = Number(bondV2Accounts.reduce((acc, ba) => acc + ba.amount.toNumber(), 0)) / 10 ** 6;
+ const relevantLock = forever ? bondV2Amount : stakedAmount;
+ if (minPoolStakeAmount > stakeAmount) {
+ setStakeAmount(minPoolStakeAmount);
+ }
+ return Math.max(minPoolStakeAmount - relevantLock, 1);
}, [
- stakedAccount?.stakeAmount,
- bondAccount?.totalStaked,
- minPoolStakeAmount,
+ stakedPool,
+ stakeAccount?.stakeAmount,
+ forever,
]);
- const transactionFeeSOL = stakedAccount ? 0.000005 : 0.0015;
- const transactionFeeMicroACS = stakedAccount ? 0 : 30;
-
const maxStakeAmount = useMemo(() => {
- return (Number(balance) / (1 + feePercentageFraction)) - transactionFeeMicroACS;
- }, [balance, feePercentageFraction, transactionFeeMicroACS]);
+ const newMax = Number(balance) - calculateFees(
+ Number(balance),
+ feeBasisPoints,
+ forever,
+ stakeAccount,
+ bondV2Accounts,
+ );
+ if (newMax < stakeAmount) {
+ setStakeAmount(newMax);
+ }
+ return newMax;
+ }, [balance, feeBasisPoints, forever, stakeAccount, bondV2Accounts]);
+
+ const fee = useMemo(() => {
+ return calculateFees(
+ stakeAmount,
+ feeBasisPoints,
+ forever,
+ stakeAccount,
+ bondV2Accounts,
+ );
+ }, [stakeAmount, feeBasisPoints, forever, stakeAccount, bondV2Accounts]);
const insufficientBalance = useMemo(() => {
return (
- minStakeAmount + transactionFeeMicroACS + minStakeAmount * feePercentageFraction > (balance ?? 0)
+ minStakeAmount + ACCOUNT_CREATION_ACS_PRICE + minStakeAmount * (feeBasisPoints || 0) / 10000 > (balance ?? 0)
);
- }, [balance, minStakeAmount, feePercentageFraction, transactionFeeMicroACS]);
-
- const insufficientSolBalance = useMemo(() => solBalance < transactionFeeSOL, [solBalance, transactionFeeMicroACS]);
+ }, [balance, minStakeAmount, feeBasisPoints]);
const invalidText = useMemo(() => {
if (insufficientBalance) {
return `Insufficient balance for locking.
- You need min. of ${formatACSCurrency(minStakeAmount + minStakeAmount * feePercentageFraction)} ACS +
- ${formatACSCurrency(transactionFeeMicroACS)} ACS protocol fee.`;
+ You need min. of ${formatACSCurrency(
+ minStakeAmount +
+ minStakeAmount * (feeBasisPoints || 0) / 1000) +
+ ACCOUNT_CREATION_ACS_PRICE
+ } ACS (including ACS fees).`;
}
return null;
}, [
insufficientBalance,
- transactionFeeMicroACS,
minStakeAmount,
- feePercentageFraction,
- stakedAccount?.stakeAmount,
+ feeBasisPoints,
+ stakeAccount?.stakeAmount,
]);
return (
@@ -459,12 +289,6 @@ export const Stake = () => {
{stakeModalOpen && !error && (
)}
@@ -479,8 +303,8 @@ export const Stake = () => {
- {stakedAccount !== undefined &&
- bondAccount !== undefined &&
+ {stakeAccount !== undefined &&
+ bondV2Accounts !== undefined &&
balance !== undefined && (
@@ -488,7 +312,7 @@ export const Stake = () => {
{!insufficientBalance ? (
- Both {poolName} and you will receive a ACS inflation rewards
+ Both {poolName} and you will get ACS rewards
split equally.
) : (
@@ -498,20 +322,6 @@ export const Stake = () => {
)}
- {!insufficientBalance && (
-
{
- setStakeAmount(value);
- }}
- />
- )}
-
{insufficientBalance && (
{
)}
{!insufficientBalance && (
-
- Lock
-
+ <>
+ {
+ setStakeAmount(value);
+ }}
+ />
+
+ {
+
+ setForever(!forever);
+ }}
+ checked={forever}
+ />
+ Forever Lock
+
+
+
+
+ {!forever ?
+ (
+ Lock
+ ) : (
+ Forever Lock
+ )
+ }
+ >
)}
-
Protocol fee: {formatACSCurrency(fee)} ACS
-
-
-
+ {fee > 0 ? (
+ <>
+
Fees: {formatACSCurrency(fee)} ACS
+
+
+
+ >
+ ) : (
+
No additional fees
+ )}
-
Transaction fee: {transactionFeeSOL} SOL
)}
- {(stakedAccount === undefined ||
- bondAccount === undefined ||
+ {(stakeAccount === undefined ||
+ bondV2Accounts === undefined ||
stakedPool == null ||
balance === undefined) && (
-
+
)}
diff --git a/src/routes/Unstake.tsx b/src/routes/Unstake.tsx
index 0685a19..bfff9dc 100644
--- a/src/routes/Unstake.tsx
+++ b/src/routes/Unstake.tsx
@@ -24,7 +24,7 @@ export const Unstake = () => {
Unlock ACS
- ACS unlocking is currently only possible in the access app.
+ ACS unlocking is currently only possible in the Access app.
@@ -34,11 +34,11 @@ export const Unstake = () => {
target='_blank'
rel='noopener'
>
- Unlock ACS on access
+ Unlock ACS on Access
- This will direct you to accessprotocol.co
+ This will redirect you to accessprotocol.co
diff --git a/yarn.lock b/yarn.lock
index 486d581..6418670 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,17 @@
# yarn lockfile v1
+"@accessprotocol/js@2.0.0-alpha.30":
+ version "2.0.0-alpha.30"
+ resolved "https://registry.yarnpkg.com/@accessprotocol/js/-/js-2.0.0-alpha.30.tgz#d39f17511bf959db61bbc411f5a50ed387ad5bd2"
+ integrity sha512-MMU49BXAqMios61N3rTSDiHiKbNbfPVJLmGIWIu0ve5ZyZxp1SlWwPN9bldN6FFXQM/WiwnZItx9kOBVwla1EA==
+ dependencies:
+ "@solana/spl-token" "^0.3.7"
+ "@solana/web3.js" "^1.66.2"
+ bn.js "^5.2.0"
+ borsh "^0.7.0"
+ bs58 "4.0.1"
+
"@ampproject/remapping@^2.1.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
@@ -1745,6 +1756,54 @@
dependencies:
buffer "~6.0.3"
+"@solana/codecs-core@2.0.0-experimental.8618508":
+ version "2.0.0-experimental.8618508"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-experimental.8618508.tgz#4f6709dd50e671267f3bea7d09209bc6471b7ad0"
+ integrity sha512-JCz7mKjVKtfZxkuDtwMAUgA7YvJcA2BwpZaA1NOLcted4OMC4Prwa3DUe3f3181ixPYaRyptbF0Ikq2MbDkYEA==
+
+"@solana/codecs-data-structures@2.0.0-experimental.8618508":
+ version "2.0.0-experimental.8618508"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-experimental.8618508.tgz#c16a704ac0f743a2e0bf73ada42d830b3402d848"
+ integrity sha512-sLpjL9sqzaDdkloBPV61Rht1tgaKq98BCtIKRuyscIrmVPu3wu0Bavk2n/QekmUzaTsj7K1pVSniM0YqCdnEBw==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-experimental.8618508"
+ "@solana/codecs-numbers" "2.0.0-experimental.8618508"
+
+"@solana/codecs-numbers@2.0.0-experimental.8618508":
+ version "2.0.0-experimental.8618508"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-experimental.8618508.tgz#d84f9ed0521b22e19125eefc7d51e217fcaeb3e4"
+ integrity sha512-EXQKfzFr3CkKKNzKSZPOOOzchXsFe90TVONWsSnVkonO9z+nGKALE0/L9uBmIFGgdzhhU9QQVFvxBMclIDJo2Q==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-experimental.8618508"
+
+"@solana/codecs-strings@2.0.0-experimental.8618508":
+ version "2.0.0-experimental.8618508"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-experimental.8618508.tgz#72457b884d9be80b59b263bcce73892b081e9402"
+ integrity sha512-b2yhinr1+oe+JDmnnsV0641KQqqDG8AQ16Z/x7GVWO+AWHMpRlHWVXOq8U1yhPMA4VXxl7i+D+C6ql0VGFp0GA==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-experimental.8618508"
+ "@solana/codecs-numbers" "2.0.0-experimental.8618508"
+
+"@solana/options@2.0.0-experimental.8618508":
+ version "2.0.0-experimental.8618508"
+ resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-experimental.8618508.tgz#95385340e85f9e8a81b2bfba089404a61c8e9520"
+ integrity sha512-fy/nIRAMC3QHvnKi63KEd86Xr/zFBVxNW4nEpVEU2OT0gCEKwHY4Z55YHf7XujhyuM3PNpiBKg/YYw5QlRU4vg==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-experimental.8618508"
+ "@solana/codecs-numbers" "2.0.0-experimental.8618508"
+
+"@solana/spl-token-metadata@^0.1.2":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.2.tgz#876e13432bd2960bd3cac16b9b0af63e69e37719"
+ integrity sha512-hJYnAJNkDrtkE2Q41YZhCpeOGU/0JgRFXbtrtOuGGeKc3pkEUHB9DDoxZAxx+XRno13GozUleyBi0qypz4c3bw==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-experimental.8618508"
+ "@solana/codecs-data-structures" "2.0.0-experimental.8618508"
+ "@solana/codecs-numbers" "2.0.0-experimental.8618508"
+ "@solana/codecs-strings" "2.0.0-experimental.8618508"
+ "@solana/options" "2.0.0-experimental.8618508"
+ "@solana/spl-type-length-value" "0.1.0"
+
"@solana/spl-token@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.5.tgz#cabf9f92d755589420b46e457b9b10a879e755aa"
@@ -1754,6 +1813,23 @@
"@solana/buffer-layout-utils" "^0.2.0"
buffer "^6.0.3"
+"@solana/spl-token@^0.3.7":
+ version "0.3.11"
+ resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.11.tgz#cdc10f9472b29b39c8983c92592cadd06627fb9a"
+ integrity sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ==
+ dependencies:
+ "@solana/buffer-layout" "^4.0.0"
+ "@solana/buffer-layout-utils" "^0.2.0"
+ "@solana/spl-token-metadata" "^0.1.2"
+ buffer "^6.0.3"
+
+"@solana/spl-type-length-value@0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b"
+ integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==
+ dependencies:
+ buffer "^6.0.3"
+
"@solana/wallet-adapter-alpha@^0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-alpha/-/wallet-adapter-alpha-0.1.9.tgz#863ae3f7108046c9e022c80023bb1b0877a6dec5"
@@ -2213,7 +2289,7 @@
"@wallet-standard/app" "^1.0.1"
"@wallet-standard/base" "^1.0.1"
-"@solana/web3.js@1.66.1", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.1":
+"@solana/web3.js@1.66.1", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.1", "@solana/web3.js@^1.66.2":
version "1.66.1"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.66.1.tgz#f11bf657e76500780a1b94fa286e9ff9e6106c71"
integrity sha512-AAVW9vGqYo8m2kMdwQtzEFuEBT21D0Z13wCzGC0lv4vay1BaqDUGSangmakeDEirtIsaAtM0gfAPu1YNLkIy/Q==
@@ -4056,6 +4132,13 @@ browserslist@^4.21.5:
node-releases "^2.0.8"
update-browserslist-db "^1.0.10"
+bs58@4.0.1, bs58@^4.0.0, bs58@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
+ integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==
+ dependencies:
+ base-x "^3.0.2"
+
bs58@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-3.1.0.tgz#d4c26388bf4804cac714141b1945aa47e5eb248e"
@@ -4063,13 +4146,6 @@ bs58@^3.0.0:
dependencies:
base-x "^1.1.0"
-bs58@^4.0.0, bs58@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
- integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==
- dependencies:
- base-x "^3.0.2"
-
bs58@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279"