Staking
Get Current Validators
우리는 네트워크를 안정적으로 확보하는 것을 돕기 위해 SOL을 스테이킹할 수 있고 보성을 얻을 수 있습니다. 스테이킹을 위해서 우리는 차례로 transaction들을 처리하는 validator들에게 SOL을 위임해야 합니다.
import { clusterApiUrl, Connection } from "@solana/web3.js";
(async () => {
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
// Get all validators, categorized by current (i.e. active) and deliquent (i.e. inactive)
const { current, delinquent } = await connection.getVoteAccounts();
console.log("current validators: ", current);
console.log("all validators: ", current.concat(delinquent));
})();
solana validators
Create Stake Account
모든 스테이킹 Instruction들은 Stake Program에 의해 다뤄집니다. 시작하기 위해 우리는 system account 표준과는 다르게 생성되고 관리되는 Stake Account를 생성합니다. 특별하게, 우리는 Account의 Stake Authority
와 Withdrawal Authority
를 설정해야 합니다.
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
StakeProgram,
Authorized,
sendAndConfirmTransaction,
Lockup,
} from "@solana/web3.js";
(async () => {
// Setup our connection and wallet
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const wallet = Keypair.generate();
// Fund our wallet with 1 SOL
const airdropSignature = await connection.requestAirdrop(
wallet.publicKey,
LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature);
// Create a keypair for our stake account
const stakeAccount = Keypair.generate();
// Calculate how much we want to stake
const minimumRent = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space
);
const amountUserWantsToStake = LAMPORTS_PER_SOL / 2; // This is can be user input. For now, we'll hardcode to 0.5 SOL
const amountToStake = minimumRent + amountUserWantsToStake;
// Setup a transaction to create our stake account
// Note: `StakeProgram.createAccount` returns a `Transaction` preconfigured with the necessary `TransactionInstruction`s
const createStakeAccountTx = StakeProgram.createAccount({
authorized: new Authorized(wallet.publicKey, wallet.publicKey), // Here we set two authorities: Stake Authority and Withdrawal Authority. Both are set to our wallet.
fromPubkey: wallet.publicKey,
lamports: amountToStake,
lockup: new Lockup(0, 0, wallet.publicKey), // Optional. We'll set this to 0 for demonstration purposes.
stakePubkey: stakeAccount.publicKey,
});
const createStakeAccountTxId = await sendAndConfirmTransaction(
connection,
createStakeAccountTx,
[
wallet,
stakeAccount, // Since we're creating a new stake account, we have that account sign as well
]
);
console.log(`Stake account created. Tx Id: ${createStakeAccountTxId}`);
// Check our newly created stake account balance. This should be 0.5 SOL.
let stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
// Verify the status of our stake account. This will start as inactive and will take some time to activate.
let stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
})();
// Setup a transaction to create our stake account
// Note: `StakeProgram.createAccount` returns a `Transaction` preconfigured with the necessary `TransactionInstruction`s
const createStakeAccountTx = StakeProgram.createAccount({
authorized: new Authorized(wallet.publicKey, wallet.publicKey), // Here we set two authorities: Stake Authority and Withdrawal Authority. Both are set to our wallet.
fromPubkey: wallet.publicKey,
lamports: amountToStake,
lockup: new Lockup(0, 0, wallet.publicKey), // Optional. We'll set this to 0 for demonstration purposes.
stakePubkey: stakeAccount.publicKey,
});
const createStakeAccountTxId = await sendAndConfirmTransaction(
connection,
createStakeAccountTx,
[
wallet,
stakeAccount, // Since we're creating a new stake account, we have that account sign as well
]
);
console.log(`Stake account created. Tx Id: ${createStakeAccountTxId}`);
// Check our newly created stake account balance. This should be 0.5 SOL.
let stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
// Verify the status of our stake account. This will start as inactive and will take some time to activate.
let stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
Delegate Stake
일단 Stake Account에 자금이 적립된다면, Stake Authority
가 validator에게 자금을 위임할 수 있습니다. 각 Stake Account는 오직 한 번에 한 validator에게만 위임될 수 있습니다. 추가로, 해당 Account에 있는 모든 Token들이 위임되거나 위임되지 않아야 합니다. 위임되고 나서 Stake Account가 활성화되기 위해서는 몇 에포크가 걸립니다.
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
StakeProgram,
Authorized,
sendAndConfirmTransaction,
Lockup,
PublicKey,
} from "@solana/web3.js";
(async () => {
// Setup our connection and wallet
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const wallet = Keypair.generate();
// Fund our wallet with 1 SOL
const airdropSignature = await connection.requestAirdrop(
wallet.publicKey,
LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature);
// Create a keypair for our stake account
const stakeAccount = Keypair.generate();
// Calculate how much we want to stake
const minimumRent = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space
);
const amountUserWantsToStake = LAMPORTS_PER_SOL / 2; // This is can be user input. For now, we'll hardcode to 0.5 SOL
const amountToStake = minimumRent + amountUserWantsToStake;
// Setup a transaction to create our stake account
// Note: `StakeProgram.createAccount` returns a `Transaction` preconfigured with the necessary `TransactionInstruction`s
const createStakeAccountTx = StakeProgram.createAccount({
authorized: new Authorized(wallet.publicKey, wallet.publicKey), // Here we set two authorities: Stake Authority and Withdrawal Authority. Both are set to our wallet.
fromPubkey: wallet.publicKey,
lamports: amountToStake,
lockup: new Lockup(0, 0, wallet.publicKey), // Optional. We'll set this to 0 for demonstration purposes.
stakePubkey: stakeAccount.publicKey,
});
const createStakeAccountTxId = await sendAndConfirmTransaction(
connection,
createStakeAccountTx,
[
wallet,
stakeAccount, // Since we're creating a new stake account, we have that account sign as well
]
);
console.log(`Stake account created. Tx Id: ${createStakeAccountTxId}`);
// Check our newly created stake account balance. This should be 0.5 SOL.
let stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
// Verify the status of our stake account. This will start as inactive and will take some time to activate.
let stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// To delegate our stake, we first have to select a validator. Here we get all validators and select the first active one.
const validators = await connection.getVoteAccounts();
const selectedValidator = validators.current[0];
const selectedValidatorPubkey = new PublicKey(selectedValidator.votePubkey);
// With a validator selected, we can now setup a transaction that delegates our stake to their vote account.
const delegateTx = StakeProgram.delegate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
votePubkey: selectedValidatorPubkey,
});
const delegateTxId = await sendAndConfirmTransaction(connection, delegateTx, [
wallet,
]);
console.log(
`Stake account delegated to ${selectedValidatorPubkey}. Tx Id: ${delegateTxId}`
);
// Check in on our stake account. It should now be activating.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
})();
// With a validator selected, we can now setup a transaction that delegates our stake to their vote account.
const delegateTx = StakeProgram.delegate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
votePubkey: selectedValidatorPubkey,
});
const delegateTxId = await sendAndConfirmTransaction(connection, delegateTx, [
wallet,
]);
console.log(
`Stake account delegated to ${selectedValidatorPubkey}. Tx Id: ${delegateTxId}`
);
// Check in on our stake account. It should now be activating.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
Get Delegator by Validators
다수의 Account들이 특정 Validator Account에 스테이킹 했을지 모릅니다. 우리는 모든 스테이커들을 조회하기 위해 getProgramAccounts
또는 getParsedProgramAccounts
API를 사용할 것입니다. 더 많은 정보는 guides section를 참조하세요. Stake Account들은 200 bytes 길이 정도고 Voter Public Key는 124 bytes부터 입니다. Reference
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
(async () => {
const STAKE_PROGRAM_ID = new PublicKey(
"Stake11111111111111111111111111111111111111"
);
const VOTE_PUB_KEY = "27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x";
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
const accounts = await connection.getParsedProgramAccounts(STAKE_PROGRAM_ID, {
filters: [
{
dataSize: 200, // number of bytes
},
{
memcmp: {
offset: 124, // number of bytes
bytes: VOTE_PUB_KEY, // base58 encoded string
},
},
],
});
console.log(`Accounts for program ${STAKE_PROGRAM_ID}: `);
console.log(
`Total number of delegators found for ${VOTE_PUB_KEY} is: ${accounts.length}`
);
if (accounts.length)
console.log(`Sample delegator:`, JSON.stringify(accounts[0]));
/*
// Output
Accounts for program Stake11111111111111111111111111111111111111:
Total number of delegators found for 27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x is: 184
Sample delegator:
{
"account": {
"data": {
"parsed": {
"info": {
"meta": {
"authorized": {
"staker": "3VDVh3rHTLkNJp6FVYbuFcaihYBFCQX5VSBZk23ckDGV",
"withdrawer": "EhYXq3ANp5nAerUpbSgd7VK2RRcxK1zNuSQ755G5Mtxx"
},
"lockup": {
"custodian": "3XdBZcURF5nKg3oTZAcfQZg8XEc5eKsx6vK8r3BdGGxg",
"epoch": 0,
"unixTimestamp": 1822867200
},
"rentExemptReserve": "2282880"
},
"stake": {
"creditsObserved": 58685367,
"delegation": {
"activationEpoch": "208",
"deactivationEpoch": "18446744073709551615",
"stake": "433005300621",
"voter": "27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x",
"warmupCooldownRate": 0.25
}
}
},
"type": "delegated"
},
"program": "stake",
"space": 200
},
"executable": false,
"lamports": 433012149261,
"owner": {
"_bn": "06a1d8179137542a983437bdfe2a7ab2557f535c8a78722b68a49dc000000000"
},
"rentEpoch": 264
},
"pubkey": {
"_bn": "0dc8b506f95e52c9ac725e714c7078799dd3268df562161411fe0916a4dc0a43"
}
}
*/
})();
const STAKE_PROGRAM_ID = new PublicKey(
"Stake11111111111111111111111111111111111111"
);
const VOTE_PUB_KEY = "27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x";
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
const accounts = await connection.getParsedProgramAccounts(STAKE_PROGRAM_ID, {
filters: [
{
dataSize: 200, // number of bytes
},
{
memcmp: {
offset: 124, // number of bytes
bytes: VOTE_PUB_KEY, // base58 encoded string
},
},
],
});
console.log(`Accounts for program ${STAKE_PROGRAM_ID}: `);
console.log(
`Total number of delegators found for ${VOTE_PUB_KEY} is: ${accounts.length}`
);
if (accounts.length)
console.log(`Sample delegator:`, JSON.stringify(accounts[0]));
/*
// Output
Accounts for program Stake11111111111111111111111111111111111111:
Total number of delegators found for 27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x is: 184
Sample delegator:
{
"account": {
"data": {
"parsed": {
"info": {
"meta": {
"authorized": {
"staker": "3VDVh3rHTLkNJp6FVYbuFcaihYBFCQX5VSBZk23ckDGV",
"withdrawer": "EhYXq3ANp5nAerUpbSgd7VK2RRcxK1zNuSQ755G5Mtxx"
},
"lockup": {
"custodian": "3XdBZcURF5nKg3oTZAcfQZg8XEc5eKsx6vK8r3BdGGxg",
"epoch": 0,
"unixTimestamp": 1822867200
},
"rentExemptReserve": "2282880"
},
"stake": {
"creditsObserved": 58685367,
"delegation": {
"activationEpoch": "208",
"deactivationEpoch": "18446744073709551615",
"stake": "433005300621",
"voter": "27MtjMSAQ2BGkXNuJEJkxFyCJT8dugGAaHJ9T7Gc6x4x",
"warmupCooldownRate": 0.25
}
}
},
"type": "delegated"
},
"program": "stake",
"space": 200
},
"executable": false,
"lamports": 433012149261,
"owner": {
"_bn": "06a1d8179137542a983437bdfe2a7ab2557f535c8a78722b68a49dc000000000"
},
"rentEpoch": 264
},
"pubkey": {
"_bn": "0dc8b506f95e52c9ac725e714c7078799dd3268df562161411fe0916a4dc0a43"
}
}
*/
Deactivate Stake
Stake Account가 위임된 후에 언제든 Stake Authority
가 그 Account를 비활성화시킬 수 있습니다. 비활성화는 완료되기까지 몇 에포크가 걸리고, SOL 출금 전에 비활성화가 먼저 요구됩니다.
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
StakeProgram,
Authorized,
sendAndConfirmTransaction,
Lockup,
PublicKey,
} from "@solana/web3.js";
(async () => {
// Setup our connection and wallet
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const wallet = Keypair.generate();
// Fund our wallet with 1 SOL
const airdropSignature = await connection.requestAirdrop(
wallet.publicKey,
LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature);
// Create a keypair for our stake account
const stakeAccount = Keypair.generate();
// Calculate how much we want to stake
const minimumRent = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space
);
const amountUserWantsToStake = LAMPORTS_PER_SOL / 2; // This is can be user input. For now, we'll hardcode to 0.5 SOL
const amountToStake = minimumRent + amountUserWantsToStake;
// Setup a transaction to create our stake account
// Note: `StakeProgram.createAccount` returns a `Transaction` preconfigured with the necessary `TransactionInstruction`s
const createStakeAccountTx = StakeProgram.createAccount({
authorized: new Authorized(wallet.publicKey, wallet.publicKey), // Here we set two authorities: Stake Authority and Withdrawal Authority. Both are set to our wallet.
fromPubkey: wallet.publicKey,
lamports: amountToStake,
lockup: new Lockup(0, 0, wallet.publicKey), // Optional. We'll set this to 0 for demonstration purposes.
stakePubkey: stakeAccount.publicKey,
});
const createStakeAccountTxId = await sendAndConfirmTransaction(
connection,
createStakeAccountTx,
[
wallet,
stakeAccount, // Since we're creating a new stake account, we have that account sign as well
]
);
console.log(`Stake account created. Tx Id: ${createStakeAccountTxId}`);
// Check our newly created stake account balance. This should be 0.5 SOL.
let stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
// Verify the status of our stake account. This will start as inactive and will take some time to activate.
let stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// To delegate our stake, we first have to select a validator. Here we get all validators and select the first active one.
const validators = await connection.getVoteAccounts();
const selectedValidator = validators.current[0];
const selectedValidatorPubkey = new PublicKey(selectedValidator.votePubkey);
// With a validator selected, we can now setup a transaction that delegates our stake to their vote account.
const delegateTx = StakeProgram.delegate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
votePubkey: selectedValidatorPubkey,
});
const delegateTxId = await sendAndConfirmTransaction(connection, delegateTx, [
wallet,
]);
console.log(
`Stake account delegated to ${selectedValidatorPubkey}. Tx Id: ${delegateTxId}`
);
// Check in on our stake account. It should now be activating.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// At anytime we can choose to deactivate our stake. Our stake account must be inactive before we can withdraw funds.
const deactivateTx = StakeProgram.deactivate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
});
const deactivateTxId = await sendAndConfirmTransaction(
connection,
deactivateTx,
[wallet]
);
console.log(`Stake account deactivated. Tx Id: ${deactivateTxId}`);
// Check in on our stake account. It should now be inactive.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
})();
// At anytime we can choose to deactivate our stake. Our stake account must be inactive before we can withdraw funds.
const deactivateTx = StakeProgram.deactivate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
});
const deactivateTxId = await sendAndConfirmTransaction(
connection,
deactivateTx,
[wallet]
);
console.log(`Stake account deactivated. Tx Id: ${deactivateTxId}`);
// Check in on our stake account. It should now be inactive.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
Withdraw Stake
비활성화되면 Withdrawal Authority
가 SOL을 System Account로 출금할 수 있습니다. Stake Account가 더 이상 위임되지 않고 0 SOL balance를 갖는 다면, 이것은 효율적으로 파괴됩니다.
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
StakeProgram,
Authorized,
sendAndConfirmTransaction,
Lockup,
PublicKey,
} from "@solana/web3.js";
(async () => {
// Setup our connection and wallet
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const wallet = Keypair.generate();
// Fund our wallet with 1 SOL
const airdropSignature = await connection.requestAirdrop(
wallet.publicKey,
LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature);
// Create a keypair for our stake account
const stakeAccount = Keypair.generate();
// Calculate how much we want to stake
const minimumRent = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space
);
const amountUserWantsToStake = LAMPORTS_PER_SOL / 2; // This is can be user input. For now, we'll hardcode to 0.5 SOL
const amountToStake = minimumRent + amountUserWantsToStake;
// Setup a transaction to create our stake account
// Note: `StakeProgram.createAccount` returns a `Transaction` preconfigured with the necessary `TransactionInstruction`s
const createStakeAccountTx = StakeProgram.createAccount({
authorized: new Authorized(wallet.publicKey, wallet.publicKey), // Here we set two authorities: Stake Authority and Withdrawal Authority. Both are set to our wallet.
fromPubkey: wallet.publicKey,
lamports: amountToStake,
lockup: new Lockup(0, 0, wallet.publicKey), // Optional. We'll set this to 0 for demonstration purposes.
stakePubkey: stakeAccount.publicKey,
});
const createStakeAccountTxId = await sendAndConfirmTransaction(
connection,
createStakeAccountTx,
[
wallet,
stakeAccount, // Since we're creating a new stake account, we have that account sign as well
]
);
console.log(`Stake account created. Tx Id: ${createStakeAccountTxId}`);
// Check our newly created stake account balance. This should be 0.5 SOL.
let stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
// Verify the status of our stake account. This will start as inactive and will take some time to activate.
let stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// To delegate our stake, we first have to select a validator. Here we get all validators and select the first active one.
const validators = await connection.getVoteAccounts();
const selectedValidator = validators.current[0];
const selectedValidatorPubkey = new PublicKey(selectedValidator.votePubkey);
// With a validator selected, we can now setup a transaction that delegates our stake to their vote account.
const delegateTx = StakeProgram.delegate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
votePubkey: selectedValidatorPubkey,
});
const delegateTxId = await sendAndConfirmTransaction(connection, delegateTx, [
wallet,
]);
console.log(
`Stake account delegated to ${selectedValidatorPubkey}. Tx Id: ${delegateTxId}`
);
// Check in on our stake account. It should now be activating.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// At anytime we can choose to deactivate our stake. Our stake account must be inactive before we can withdraw funds.
const deactivateTx = StakeProgram.deactivate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
});
const deactivateTxId = await sendAndConfirmTransaction(
connection,
deactivateTx,
[wallet]
);
console.log(`Stake account deactivated. Tx Id: ${deactivateTxId}`);
// Check in on our stake account. It should now be inactive.
stakeStatus = await connection.getStakeActivation(stakeAccount.publicKey);
console.log(`Stake account status: ${stakeStatus.state}`);
// Once deactivated, we can withdraw our SOL back to our main wallet
const withdrawTx = StakeProgram.withdraw({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
toPubkey: wallet.publicKey,
lamports: stakeBalance, // Withdraw the full balance at the time of the transaction
});
const withdrawTxId = await sendAndConfirmTransaction(connection, withdrawTx, [
wallet,
]);
console.log(`Stake account withdrawn. Tx Id: ${withdrawTxId}`);
// Confirm that our stake account balance is now 0
stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);
})();
// Once deactivated, we can withdraw our SOL back to our main wallet
const withdrawTx = StakeProgram.withdraw({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: wallet.publicKey,
toPubkey: wallet.publicKey,
lamports: stakeBalance, // Withdraw the full balance at the time of the transaction
});
const withdrawTxId = await sendAndConfirmTransaction(connection, withdrawTx, [
wallet,
]);
console.log(`Stake account withdrawn. Tx Id: ${withdrawTxId}`);
// Confirm that our stake account balance is now 0
stakeBalance = await connection.getBalance(stakeAccount.publicKey);
console.log(`Stake account balance: ${stakeBalance / LAMPORTS_PER_SOL} SOL`);