Instantly share code, notes, and snippets.
Forked from BryanMorgan/solana-rent-share-example-client.ts
Created
December 6, 2021 00:21
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save snowsky/ac5dd5ad980f0d559a68c6ffbafc1d5a to your computer and use it in GitHub Desktop.
Solana Rent Share Client Example - basic agreement setup, initialization, and rental payment
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| Connection, | |
| Cluster, | |
| Keypair, | |
| clusterApiUrl, | |
| PublicKey, | |
| sendAndConfirmTransaction, | |
| SystemProgram, | |
| Transaction, | |
| SYSVAR_RENT_PUBKEY, | |
| TransactionInstruction, | |
| } from "@solana/web3.js"; | |
| import BN from "bn.js"; | |
| import * as borsh from "borsh"; | |
| enum Instructions { | |
| Initialize = 0, | |
| PayRent, | |
| TerminateEarly, | |
| } | |
| enum AgreementStatus { | |
| Uninitialized = 0, | |
| Active, | |
| Completed, | |
| Violated, | |
| } | |
| enum RentalDuration { | |
| Months = 0, | |
| } | |
| export type ErrorData = { | |
| error: { | |
| name: string; | |
| message: string; | |
| }; | |
| }; | |
| export type RentShareAccountData = { | |
| payeePublicKey: PublicKey; | |
| payerPublicKey: PublicKey; | |
| deposit: number; | |
| rentAmount: number; | |
| duration: number; | |
| durationUnit: number; | |
| remainingPayments: number; | |
| }; | |
| class RentShareAccount { | |
| status: AgreementStatus; | |
| payeePublicKey: number[]; | |
| payerPublicKey: number[]; | |
| deposit = 0; | |
| rentAmount = 0; | |
| duration = 0; | |
| durationUnit = 0; | |
| remainingPayments = 0; | |
| constructor(data: RentShareAccountData) { | |
| this.status = AgreementStatus.Uninitialized; | |
| if (data.payeePublicKey instanceof PublicKey) { | |
| this.payeePublicKey = Array.from(data.payeePublicKey.toBytes()); | |
| } else { | |
| this.payeePublicKey = Array.from(data.payeePublicKey); | |
| } | |
| if (data.payerPublicKey instanceof PublicKey) { | |
| this.payerPublicKey = Array.from(data.payerPublicKey.toBytes()); | |
| } else { | |
| this.payerPublicKey = Array.from(data.payerPublicKey); | |
| } | |
| this.deposit = data.deposit; | |
| this.rentAmount = data.rentAmount; | |
| this.duration = data.duration; | |
| this.durationUnit = data.durationUnit; | |
| this.remainingPayments = data.remainingPayments; | |
| } | |
| } | |
| /** | |
| * Borsh schema definition for rent share account data | |
| */ | |
| const RentShareDataSchema = new Map([ | |
| [ | |
| RentShareAccount, | |
| { | |
| kind: "struct", | |
| fields: [ | |
| ["status", "u8"], | |
| ["payeePublicKey", [32]], | |
| ["payerPublicKey", [32]], | |
| ["deposit", "u64"], | |
| ["rentAmount", "u64"], | |
| ["duration", "u64"], | |
| ["durationUnit", "u8"], | |
| ["remainingPayments", "u64"], | |
| ], | |
| }, | |
| ], | |
| ]); | |
| /** | |
| * The expected size of each rent share account. | |
| */ | |
| const RENT_SCHEMA_SIZE = borsh.serialize( | |
| RentShareDataSchema, | |
| new RentShareAccount({ | |
| payeePublicKey: PublicKey.default, | |
| payerPublicKey: PublicKey.default, | |
| deposit: 0, | |
| rentAmount: 0, | |
| duration: 0, | |
| durationUnit: 0, | |
| remainingPayments: 0, | |
| }) | |
| ).length; | |
| /** | |
| * Create Rent Share account if it doesn't already exist | |
| */ | |
| export async function createRentShareAccount( | |
| accountOwner: Keypair, | |
| seed: string | |
| ): Promise<PublicKey> { | |
| const programId = new PublicKey(process.env.PROGRAM_ID || ""); | |
| const clusterUrl = clusterApiUrl(process.env.solanaEnvironment as Cluster); | |
| const connection = new Connection(clusterUrl, "confirmed"); | |
| const rentAgreementPublicKey = await PublicKey.createWithSeed( | |
| accountOwner.publicKey, | |
| seed, | |
| programId | |
| ); | |
| const rentShareAccountInfo = await connection.getAccountInfo( | |
| rentAgreementPublicKey | |
| ); | |
| if (rentShareAccountInfo === null) { | |
| const lamports = await connection.getMinimumBalanceForRentExemption( | |
| RENT_SCHEMA_SIZE | |
| ); | |
| console.log( | |
| `Lamports for rent: ${lamports}. Schema size: ${RENT_SCHEMA_SIZE}` | |
| ); | |
| const transaction = new Transaction().add( | |
| SystemProgram.createAccountWithSeed({ | |
| fromPubkey: accountOwner.publicKey, | |
| basePubkey: accountOwner.publicKey, | |
| seed, | |
| newAccountPubkey: rentAgreementPublicKey, | |
| lamports, | |
| space: RENT_SCHEMA_SIZE, | |
| programId, | |
| }) | |
| ); | |
| await sendAndConfirmTransaction(connection, transaction, [accountOwner]); | |
| } | |
| return rentAgreementPublicKey; | |
| } | |
| /** | |
| * Initialize a rental agreement between a payer and payee specified in account data | |
| */ | |
| export async function initializeRentShareAccount( | |
| rentAgreementPublicKey: PublicKey, | |
| accountOwner: Keypair, | |
| data: RentShareAccountData | |
| ): Promise<void> { | |
| const programId = new PublicKey(process.env.PROGRAM_ID || ""); | |
| const clusterUrl = clusterApiUrl(process.env.solanaEnvironment as Cluster); | |
| const connection = new Connection(clusterUrl, "confirmed"); | |
| const instruction = Instructions.Initialize; | |
| const transactionInstruction = new TransactionInstruction({ | |
| keys: [ | |
| { pubkey: rentAgreementPublicKey, isSigner: false, isWritable: true }, | |
| { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, | |
| ], | |
| programId, | |
| data: Buffer.from( | |
| Uint8Array.of( | |
| instruction, | |
| ...Array.from(data.payeePublicKey.toBytes()), | |
| ...Array.from(data.payerPublicKey.toBytes()), | |
| ...new BN(data.deposit).toArray("le", 8), | |
| ...new BN(data.rentAmount).toArray("le", 8), | |
| ...new BN(data.duration).toArray("le", 8), | |
| data.durationUnit | |
| ) | |
| ), | |
| }); | |
| const signature = await sendAndConfirmTransaction( | |
| connection, | |
| new Transaction().add(transactionInstruction), | |
| [accountOwner] | |
| ); | |
| } | |
| /** | |
| * Record a rent transaction between a payer and payee | |
| */ | |
| export async function recordRentPayment( | |
| rentAgreementPublicKey: PublicKey, | |
| payerPrivateKey: Keypair, | |
| payeePrivateKey: Keypair, | |
| rentAmount: number | |
| ): Promise<string> { | |
| const programId = new PublicKey(process.env.PROGRAM_ID || ""); | |
| const clusterUrl = clusterApiUrl(process.env.solanaEnvironment as Cluster); | |
| const connection = new Connection(clusterUrl, "confirmed"); | |
| const instruction = Instructions.PayRent; | |
| const transactionInstruction = new TransactionInstruction({ | |
| keys: [ | |
| { pubkey: rentAgreementPublicKey, isSigner: false, isWritable: true }, | |
| { pubkey: payeePrivateKey.publicKey, isSigner: false, isWritable: true }, | |
| { pubkey: payerPrivateKey.publicKey, isSigner: true, isWritable: true }, | |
| { pubkey: SystemProgram.programId, isSigner: false, isWritable: true }, | |
| ], | |
| programId, | |
| data: Buffer.from( | |
| Uint8Array.of(instruction, ...new BN(rentAmount).toArray("le", 8)) | |
| ), | |
| }); | |
| const signature = await sendAndConfirmTransaction( | |
| connection, | |
| new Transaction().add(transactionInstruction), | |
| [payerPrivateKey] | |
| ); | |
| return signature; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment