UNPKG

@coral-xyz/barter-sdk

Version:
282 lines 12.3 kB
/* * Copyright (C) 2023 Blue Coral, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { createAssociatedTokenAccountIdempotentInstruction, createSyncNativeInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, } from "@solana/spl-token"; import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; const MAX_SETTLEMENT_ITEMS = 6; // FIXME: export const PROGRAM_ID = new PublicKey("bartdDGTCTb4V2pavcXzoBsptUzq5mMfjfXwzdzDg8a"); /** * Creates the full transaction for the `cancel` instruction set. * @export * @param {...Parameters<typeof createCancelInstruction>} args * @returns {Promise<Transaction>} */ export function createCancelTransaction(...args) { return __awaiter(this, void 0, void 0, function* () { const ix = yield createCancelInstruction(...args); return new Transaction().add(ix); }); } /** * Builds the single instruction instance for `cancel`. * @export * @param {Program<Barter>} program * @param {CancelParameters} opts * @returns {Promise<TransactionInstruction>} */ export function createCancelInstruction(program, opts) { return __awaiter(this, void 0, void 0, function* () { const state = yield program.account.agreement.fetch(opts.agreement); if (!state) { throw new Error("the argued agreement does not exist"); } const remainingAccounts = state.offer.map(o => { const ata = getAssociatedTokenAddressSync(o.item.mint, program.provider.publicKey); return { pubkey: ata, isSigner: false, isWritable: true }; }); const delegate = deriveDelegateAddress(program.provider.publicKey)[0]; return program.methods .cancel() .accounts({ agreement: opts.agreement, delegate }) .remainingAccounts(remainingAccounts) .instruction(); }); } /** * Creates the full transactino for the `create` instruction set, possibly * including the convertion of SOL -> wSOL for native barter items. * @export * @param {...Parameters<typeof createCreateInstruction>} args * @returns {Promise<Transaction>} */ export function createCreateTransaction(...args) { return __awaiter(this, void 0, void 0, function* () { let tx = new Transaction(); // Convert native SOL barter item to wSOL prior to agreement creation if found const nativeItem = args[1].offer.find(o => o.mint.equals(SystemProgram.programId)); if (nativeItem) { tx = tx.add(...createWrappedSolInstructions(args[0].provider.publicKey, nativeItem)); } const ix = yield createCreateInstruction(...args); return tx.add(ix); }); } /** * Builds the single instruction instance for `create`. * @export * @param {Program<Barter>} program * @param {CreateParameters} opts * @returns {Promise<TransactionInstruction>} */ export function createCreateInstruction(program, opts) { return __awaiter(this, void 0, void 0, function* () { const agreement = deriveAgreementAddress(program.provider.publicKey, opts.participant)[0]; const offerAmountsOnly = []; const remainingAccounts = []; for (const o of opts.offer) { offerAmountsOnly.push(o.amount); const ata = getAssociatedTokenAddressSync(o.mint, program.provider.publicKey); remainingAccounts.push({ pubkey: ata, isSigner: false, isWritable: true }); } return program.methods .create(offerAmountsOnly, opts.expecting) .accounts({ agreement, participant: opts.participant, }) .remainingAccounts(remainingAccounts) .instruction(); }); } /** * Creates the full transaction for the `finalize` instruction set, possibly * including the convertion of SOL -> wSOL for native barter items. * @export * @param {...Parameters<typeof createFinalizeInstruction>} args * @returns {Promise<Transaction>} */ export function createFinalizeTransaction(...args) { return __awaiter(this, void 0, void 0, function* () { let tx = new Transaction(); // Convert native SOL barter item to wSOL prior to agreement creation if found const nativeItem = args[1].offer.find(o => o.mint.equals(SystemProgram.programId)); if (nativeItem) { tx = tx.add(...createWrappedSolInstructions(args[0].provider.publicKey, nativeItem)); } const ix = yield createFinalizeInstruction(...args); return tx.add(ix); }); } /** * Builds the single instruction instance for `finalize`. * @export * @param {Program<Barter>} program * @param {FinalizeParameters} opts * @returns {Promise<TransactionInstruction>} */ export function createFinalizeInstruction(program, opts) { return __awaiter(this, void 0, void 0, function* () { const offerAmountsOnly = []; const remainingAccounts = []; for (const o of opts.offer) { offerAmountsOnly.push(o.amount); const ata = getAssociatedTokenAddressSync(o.mint, program.provider.publicKey); remainingAccounts.push({ pubkey: ata, isSigner: false, isWritable: true }); } return program.methods .finalize(offerAmountsOnly, opts.expecting) .accounts({ agreement: opts.agreement, initiater: opts.initiater, }) .remainingAccounts(remainingAccounts) .instruction(); }); } /** * Creates the full transaction for the `settle` instruction set, including * the idempotent creation of the destination associated token accounts for settlement. * @export * @param {...Parameters<typeof createSettleInstructions>} args * @returns {Promise<Transaction>} */ export function createSettleTransaction(...args) { return __awaiter(this, void 0, void 0, function* () { const ixs = yield createSettleInstructions(...args); return new Transaction().add(...ixs); }); } /** * Builds the list of ordered instructions for processing the `settle` * instruction and the pre-instructions that are required. * @export * @param {Program<Barter>} program * @param {SettleParameters} opts * @returns {Promise<TransactionInstruction[]>} */ export function createSettleInstructions(program, opts) { return __awaiter(this, void 0, void 0, function* () { const state = yield program.account.agreement.fetch(opts.agreement); if (!state) { throw new Error("the argued agreement does not exist"); } // Filter for only unsettled items in the initiater's offer list const unsettledOfferItems = state.offer.reduce((acc, curr) => (curr.settled ? acc : [...acc, Object.assign({ sender: state.initiater, recipient: state.participant }, curr)]), []); // Filter for only unsettled items in the participant's offer list const unsettledExpectingItems = state.expecting.reduce((acc, curr) => (curr.settled ? acc : [...acc, Object.assign({ sender: state.participant, recipient: state.initiater }, curr)]), []); // Equally parallel merge the two unsettled item arrays and pick the top `MAX_SETTLEMENT_ITEMS` items const unsettled = mergeArraysEqually(unsettledOfferItems, unsettledExpectingItems).slice(0, MAX_SETTLEMENT_ITEMS); const remainingAccounts = []; const preInstructions = []; for (const x of unsettled) { const source = getAssociatedTokenAddressSync(x.item.mint, x.sender); const destination = getAssociatedTokenAddressSync(x.item.mint, x.recipient); // Append the source and destination token accounts to the remaining accounts array remainingAccounts.push({ pubkey: source, isSigner: false, isWritable: true }, { pubkey: destination, isSigner: false, isWritable: true }); // Append an instruction to idempotently create the destination associated token account to receive the item preInstructions.push(createAssociatedTokenAccountIdempotentInstruction(program.provider.publicKey, destination, x.recipient, x.item.mint)); } const initiaterDelegate = deriveDelegateAddress(state.initiater)[0]; const participantDelegate = deriveDelegateAddress(state.participant)[0]; const ix = yield program.methods .settle() .accounts({ agreement: opts.agreement, initiater: state.initiater, initiaterDelegate, participant: state.participant, participantDelegate, }) .remainingAccounts(remainingAccounts) .instruction(); return [...preInstructions, ix]; }); } /** * Derive the program address and nonce for an `Agreement` PDA. * @export * @param {PublicKey} initiater * @param {PublicKey} participant * @returns {[PublicKey, number]} */ export function deriveAgreementAddress(initiater, participant) { // Rust orders byte slices based on the length of the slice during a memcmp operation const users = [initiater.toBytes(), participant.toBytes()].sort((a, b) => (a.length <= b.length ? 0 : 1)); return PublicKey.findProgramAddressSync([Buffer.from("agreement"), ...users], PROGRAM_ID); } /** * Derive the program address and nonce for a `Delegate` PDA. * @export * @param {PublicKey} authority * @returns {[PublicKey, number]} */ export function deriveDelegateAddress(authority) { return PublicKey.findProgramAddressSync([Buffer.from("delegate"), authority.toBytes()], PROGRAM_ID); } /** * Returns the array of transaction instructions required to convert native SOL * into wSOL for token account delegation as a barter item. * @param {PublicKey} owner * @param {IdlBarterItem} item * @returns {TransactionInstruction[]} */ function createWrappedSolInstructions(owner, item) { // In-place update the mint value on the barter item to be the wSOL mint public key item.mint = NATIVE_MINT; // Calculate wSOL associated token account address const ata = getAssociatedTokenAddressSync(NATIVE_MINT, owner); return [ // Idempotently create the wSOL token account createAssociatedTokenAccountIdempotentInstruction(owner, ata, owner, NATIVE_MINT), // Transfer the barter item amount as lamports into wSOL ATA SystemProgram.transfer({ fromPubkey: owner, toPubkey: ata, lamports: item.amount, }), // Sync the lamport transfer with the wSOL ATA balance createSyncNativeInstruction(ata), ]; } /** * Equal and parallel merge of two generic arrays. * @template T * @param {T[]} a * @param {T[]} b * @returns {T[]} */ function mergeArraysEqually(a, b) { const merged = []; for (let i = 0; i < Math.max(a.length, b.length); i++) { if (i < a.length) { merged.push(a[i]); } if (i < b.length) { merged.push(b[i]); } } return merged; } //# sourceMappingURL=instructions.js.map