Skip to content

Commit 49b7561

Browse files
committed
Add CLI management tool (WIP)
1 parent 5f0b51e commit 49b7561

20 files changed

+582
-8
lines changed

.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ANCHOR_PROVIDER_URL=
2+
ANCHOR_WALLET=

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ test-ledger
88

99
devnet.json
1010
yarn-error.log
11+
12+
.env*
13+
!.env.example

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@
2828
"@types/chai-as-promised": "^7.1.5",
2929
"@types/mocha": "^9.0.0",
3030
"@types/node": "^18",
31+
"@types/readline-sync": "^1.4.8",
3132
"@typescript-eslint/eslint-plugin": "^5.0.0",
3233
"@typescript-eslint/parser": "^5.42.1",
3334
"chai": "^4.3.4",
3435
"chai-as-promised": "^7.1.1",
36+
"chalk": "^5.3.0",
37+
"cli-table3": "^0.6.5",
3538
"eslint": "^8.0.1",
3639
"eslint-config-prettier": "^8.5.0",
3740
"eslint-config-standard-with-typescript": "^23.0.0",
@@ -42,6 +45,7 @@
4245
"eslint-plugin-react": "^7.31.10",
4346
"mocha": "^10.2.0",
4447
"prettier": "^2.6.2",
48+
"readline-sync": "^1.4.10",
4549
"ts-mocha": "^10.0.0",
4650
"typescript": "^4.3.5"
4751
}

packages/fund-sender/client/index.ts

+17
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,23 @@ export class FundSenderClient {
373373
return this;
374374
}
375375

376+
public async sendFromState(
377+
): Promise<FundSenderClient> {
378+
if (!this.config) {
379+
throw new Error("Client not initialized");
380+
}
381+
382+
await this.program.methods
383+
.sendFromState()
384+
.accounts({
385+
state: this.stateAddress,
386+
})
387+
.rpc()
388+
.then(confirm(this.provider.connection));
389+
390+
return this
391+
}
392+
376393
/**
377394
* Sends specified amount of NFTs from input account to hold account.
378395
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { FundSenderClient } from "../client";
2+
import { logBalance } from "./lib/util";
3+
import {PublicKey} from "@solana/web3.js";
4+
5+
// USAGE: yarn ts-node packages/fund-sender/getStateFromAddress.ts stateAddress
6+
const stateAddress = process.argv[2];
7+
8+
(async () => {
9+
const client = await FundSenderClient.fetch(new PublicKey(stateAddress));
10+
const log = logBalance(client);
11+
12+
console.log("state address", stateAddress);
13+
console.log("state account data", {
14+
destinationName: client.config.destinationName,
15+
updateAuthority: client.config.updateAuthority.toBase58(),
16+
destinationAccount: client.config.destinationAccount.toBase58(),
17+
certificateVault: client.config.certificateVault.toBase58(),
18+
spendThreshold: client.config.spendThreshold.toNumber(),
19+
});
20+
console.log("input address", client.getInputAccount().toBase58());
21+
22+
await log("input token", client.getInputAccount());
23+
})().catch(console.error);

packages/fund-sender/scripts/registerState.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const sunriseTreasuryAddress = new PublicKey(
1717
process.env.TREASURY_ADDRESS ?? defaultSunriseTreasuryAddress
1818
);
1919

20-
// USAGE: yarn ts-node packages/fund-sender/resgisterState.ts destinationName destinationAccount
20+
// USAGE: yarn ts-node packages/fund-sender/registerState.ts destinationName destinationAccount
2121
const destinationName = process.argv[2];
2222
const destinationAccount = new PublicKey(process.argv[3]);
2323

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FundSenderClient } from "../client";
2+
import { PublicKey } from "@solana/web3.js";
3+
4+
// mainnet Sunrise
5+
const defaultSunriseStateAddress =
6+
"43m66crxGfXSJpmx5wXRoFuHubhHA1GCvtHgmHW6cM1P";
7+
const sunriseStateAddress = new PublicKey(
8+
process.env.STATE_ADDRESS ?? defaultSunriseStateAddress
9+
);
10+
11+
// USAGE: yarn ts-node packages/fund-sender/sendFromState.ts destinationName
12+
const destinationName = process.argv[2];
13+
14+
(async () => {
15+
const stateAddress = FundSenderClient.getStateAddressFromSunriseAddress(
16+
sunriseStateAddress,
17+
destinationName
18+
);
19+
const client = await FundSenderClient.fetch(stateAddress);
20+
21+
console.log("state address", stateAddress.toBase58());
22+
console.log("input address", client.getInputAccount().toBase58());
23+
24+
const stateBalance = await client.provider.connection.getBalance(stateAddress);
25+
console.log("state balance", stateBalance);
26+
27+
console.log("Sending fund...");
28+
await client.sendFromState();
29+
})().catch(console.error);

packages/idl/fund_sender.json

+54
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,60 @@
100100
}
101101
]
102102
},
103+
{
104+
"name": "send_from_state",
105+
"discriminator": [
106+
119,
107+
32,
108+
214,
109+
129,
110+
74,
111+
57,
112+
186,
113+
185
114+
],
115+
"accounts": [
116+
{
117+
"name": "state",
118+
"writable": true
119+
},
120+
{
121+
"name": "input_account",
122+
"writable": true,
123+
"pda": {
124+
"seeds": [
125+
{
126+
"kind": "const",
127+
"value": [
128+
105,
129+
110,
130+
112,
131+
117,
132+
116,
133+
95,
134+
97,
135+
99,
136+
99,
137+
111,
138+
117,
139+
110,
140+
116
141+
]
142+
},
143+
{
144+
"kind": "account",
145+
"path": "state"
146+
}
147+
]
148+
}
149+
},
150+
{
151+
"name": "system_program",
152+
"address": "11111111111111111111111111111111"
153+
}
154+
],
155+
"args": []
156+
},
103157
{
104158
"name": "send_fund",
105159
"discriminator": [

packages/types/fund_sender.ts

+54
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,60 @@ export type FundSender = {
106106
}
107107
]
108108
},
109+
{
110+
"name": "sendFromState",
111+
"discriminator": [
112+
119,
113+
32,
114+
214,
115+
129,
116+
74,
117+
57,
118+
186,
119+
185
120+
],
121+
"accounts": [
122+
{
123+
"name": "state",
124+
"writable": true
125+
},
126+
{
127+
"name": "inputAccount",
128+
"writable": true,
129+
"pda": {
130+
"seeds": [
131+
{
132+
"kind": "const",
133+
"value": [
134+
105,
135+
110,
136+
112,
137+
117,
138+
116,
139+
95,
140+
97,
141+
99,
142+
99,
143+
111,
144+
117,
145+
110,
146+
116
147+
]
148+
},
149+
{
150+
"kind": "account",
151+
"path": "state"
152+
}
153+
]
154+
}
155+
},
156+
{
157+
"name": "systemProgram",
158+
"address": "11111111111111111111111111111111"
159+
}
160+
],
161+
"args": []
162+
},
109163
{
110164
"name": "sendFund",
111165
"discriminator": [

packages/yield-router/client/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export interface YieldRouterConfig {
9595
spendThreshold: BN;
9696
}
9797

98-
type InitialisedClient = YieldRouterClient & {
98+
export type InitialisedClient = YieldRouterClient & {
9999
config: YieldRouterConfig;
100100
};
101101

packages/yield-router/scripts/updateState.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ const sunriseStateAddress = new PublicKey(
1616
);
1717

1818
(async () => {
19+
const stateAddress =
20+
YieldRouterClient.getStateAddressFromSunriseAddress(sunriseStateAddress);
21+
const client = await YieldRouterClient.fetch(stateAddress);
22+
23+
console.log("Updating yield router state:", stateAddress.toBase58());
24+
1925
// Get new update authority
2026
let newUpdateAuthority: PublicKey | undefined;
2127

@@ -84,10 +90,6 @@ const sunriseStateAddress = new PublicKey(
8490

8591
rl.close();
8692

87-
const stateAddress =
88-
YieldRouterClient.getStateAddressFromSunriseAddress(sunriseStateAddress);
89-
const client = await YieldRouterClient.fetch(stateAddress);
90-
9193
// Update output yield accounts and proportions
9294
if (sumProportions === 100 && answer.toLocaleLowerCase() === "y") {
9395
const state = await client.updateOutputYieldAccounts(

programs/fund-sender/src/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ pub mod fund_sender {
4141
Ok(())
4242
}
4343

44+
pub fn send_from_state(
45+
ctx: Context<SendFromState>,
46+
) -> Result<()> {
47+
let state = &ctx.accounts.state;
48+
let amount = state.get_lamports();
49+
50+
if amount > 0 {
51+
**state.to_account_info().try_borrow_mut_lamports()? -= amount;
52+
**ctx.accounts.input_account.try_borrow_mut_lamports()? += amount;
53+
}
54+
55+
Ok(())
56+
}
57+
4458
pub fn send_fund<'info>(
4559
ctx: Context<'_, '_, '_, 'info, SendFund<'info>>,
4660
amount: u64,

programs/fund-sender/src/utils/state.rs

+20
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ pub struct SendFund<'info> {
9595
pub system_program: Program<'info, System>,
9696
}
9797

98+
/// If someone sent funds to the state account, this instruction will send it to the input account
99+
#[derive(Accounts)]
100+
// #[instruction(sunrise_state: Pubkey, destination_name: String)]
101+
pub struct SendFromState<'info> {
102+
#[account(
103+
mut,
104+
// seeds = [STATE, &destination_name.as_bytes(), sunrise_state.key().as_ref()],
105+
// bump,
106+
)]
107+
pub state: Account<'info, State>,
108+
#[account(
109+
mut,
110+
seeds = [INPUT_ACCOUNT, state.key().as_ref()],
111+
bump = state.input_account_bump,
112+
)]
113+
/// CHECK: Must be correctly derived from the state
114+
pub input_account: UncheckedAccount<'info>,
115+
pub system_program: Program<'info, System>,
116+
}
117+
98118
#[derive(Accounts)]
99119
pub struct StoreCertificates<'info> {
100120
// to send the received retired climate token to a hold account

scripts/manager.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {showData} from "./util";
2+
import {showMenu} from "./prompt";
3+
4+
await showData();
5+
6+
showMenu();

scripts/prompt.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import chalk from 'chalk';
2+
import readlineSync from 'readline-sync';
3+
import {showData} from "./util";
4+
import {submenuRouteToRecipient} from "./submenu/routeToRecipient";
5+
import {submenuAllocateYield} from "./submenu/allocateYield";
6+
7+
export const showMenu = async () => {
8+
console.log(chalk.magentaBright('\nChoose an option:'));
9+
console.log(chalk.cyanBright('1) Refresh'));
10+
console.log(chalk.cyanBright('2) Allocate Yield'));
11+
console.log(chalk.cyanBright('3) Route Funds to Recipient'));
12+
console.log(chalk.cyanBright('4) Update Proportions'));
13+
console.log(chalk.cyanBright('5) Add Recipient'));
14+
console.log(chalk.cyanBright('6) Remove Recipient'));
15+
console.log(chalk.cyanBright('7) Quit'));
16+
17+
const choice = readlineSync.keyIn(chalk.yellow('\nEnter your choice: '), { limit: '$<1-7>' });
18+
19+
switch (choice) {
20+
case '1':
21+
console.log(chalk.green('Refreshing...'));
22+
await showData();
23+
break;
24+
case '2':
25+
await submenuAllocateYield();
26+
break;
27+
case '3':
28+
await submenuRouteToRecipient();
29+
break;
30+
case '4':
31+
console.log(chalk.green('Updating proportions...'));
32+
// Call your update proportions function here
33+
break;
34+
case '5':
35+
console.log(chalk.green('Adding recipient...'));
36+
// Call your add recipient function here
37+
break;
38+
case '6':
39+
console.log(chalk.green('Removing recipient...'));
40+
// Call your remove recipient function here
41+
break;
42+
case '7':
43+
console.log(chalk.green('Exiting...'));
44+
process.exit(0);
45+
break;
46+
default:
47+
console.log(chalk.red('Invalid choice, please try again.'));
48+
showMenu(); // Re-display menu for invalid input
49+
break;
50+
}
51+
showMenu();
52+
};

0 commit comments

Comments
 (0)