@coral-xyz/barter-sdk
Version:
Node.js client for the Barter protocol
295 lines • 13.4 kB
JavaScript
"use strict";
/*
* 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deriveDelegateAddress = exports.deriveAgreementAddress = exports.createSettleInstructions = exports.createSettleTransaction = exports.createFinalizeInstruction = exports.createFinalizeTransaction = exports.createCreateInstruction = exports.createCreateTransaction = exports.createCancelInstruction = exports.createCancelTransaction = exports.PROGRAM_ID = void 0;
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const MAX_SETTLEMENT_ITEMS = 6; // FIXME:
exports.PROGRAM_ID = new web3_js_1.PublicKey("bartdDGTCTb4V2pavcXzoBsptUzq5mMfjfXwzdzDg8a");
/**
* Creates the full transaction for the `cancel` instruction set.
* @export
* @param {...Parameters<typeof createCancelInstruction>} args
* @returns {Promise<Transaction>}
*/
function createCancelTransaction(...args) {
return __awaiter(this, void 0, void 0, function* () {
const ix = yield createCancelInstruction(...args);
return new web3_js_1.Transaction().add(ix);
});
}
exports.createCancelTransaction = createCancelTransaction;
/**
* Builds the single instruction instance for `cancel`.
* @export
* @param {Program<Barter>} program
* @param {CancelParameters} opts
* @returns {Promise<TransactionInstruction>}
*/
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 = (0, spl_token_1.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();
});
}
exports.createCancelInstruction = createCancelInstruction;
/**
* 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>}
*/
function createCreateTransaction(...args) {
return __awaiter(this, void 0, void 0, function* () {
let tx = new web3_js_1.Transaction();
// Convert native SOL barter item to wSOL prior to agreement creation if found
const nativeItem = args[1].offer.find(o => o.mint.equals(web3_js_1.SystemProgram.programId));
if (nativeItem) {
tx = tx.add(...createWrappedSolInstructions(args[0].provider.publicKey, nativeItem));
}
const ix = yield createCreateInstruction(...args);
return tx.add(ix);
});
}
exports.createCreateTransaction = createCreateTransaction;
/**
* Builds the single instruction instance for `create`.
* @export
* @param {Program<Barter>} program
* @param {CreateParameters} opts
* @returns {Promise<TransactionInstruction>}
*/
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 = (0, spl_token_1.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();
});
}
exports.createCreateInstruction = createCreateInstruction;
/**
* 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>}
*/
function createFinalizeTransaction(...args) {
return __awaiter(this, void 0, void 0, function* () {
let tx = new web3_js_1.Transaction();
// Convert native SOL barter item to wSOL prior to agreement creation if found
const nativeItem = args[1].offer.find(o => o.mint.equals(web3_js_1.SystemProgram.programId));
if (nativeItem) {
tx = tx.add(...createWrappedSolInstructions(args[0].provider.publicKey, nativeItem));
}
const ix = yield createFinalizeInstruction(...args);
return tx.add(ix);
});
}
exports.createFinalizeTransaction = createFinalizeTransaction;
/**
* Builds the single instruction instance for `finalize`.
* @export
* @param {Program<Barter>} program
* @param {FinalizeParameters} opts
* @returns {Promise<TransactionInstruction>}
*/
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 = (0, spl_token_1.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();
});
}
exports.createFinalizeInstruction = createFinalizeInstruction;
/**
* 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>}
*/
function createSettleTransaction(...args) {
return __awaiter(this, void 0, void 0, function* () {
const ixs = yield createSettleInstructions(...args);
return new web3_js_1.Transaction().add(...ixs);
});
}
exports.createSettleTransaction = createSettleTransaction;
/**
* 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[]>}
*/
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 = (0, spl_token_1.getAssociatedTokenAddressSync)(x.item.mint, x.sender);
const destination = (0, spl_token_1.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((0, spl_token_1.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];
});
}
exports.createSettleInstructions = createSettleInstructions;
/**
* Derive the program address and nonce for an `Agreement` PDA.
* @export
* @param {PublicKey} initiater
* @param {PublicKey} participant
* @returns {[PublicKey, number]}
*/
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 web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("agreement"), ...users], exports.PROGRAM_ID);
}
exports.deriveAgreementAddress = deriveAgreementAddress;
/**
* Derive the program address and nonce for a `Delegate` PDA.
* @export
* @param {PublicKey} authority
* @returns {[PublicKey, number]}
*/
function deriveDelegateAddress(authority) {
return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("delegate"), authority.toBytes()], exports.PROGRAM_ID);
}
exports.deriveDelegateAddress = deriveDelegateAddress;
/**
* 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 = spl_token_1.NATIVE_MINT;
// Calculate wSOL associated token account address
const ata = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, owner);
return [
// Idempotently create the wSOL token account
(0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(owner, ata, owner, spl_token_1.NATIVE_MINT),
// Transfer the barter item amount as lamports into wSOL ATA
web3_js_1.SystemProgram.transfer({
fromPubkey: owner,
toPubkey: ata,
lamports: item.amount,
}),
// Sync the lamport transfer with the wSOL ATA balance
(0, spl_token_1.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