@coral-xyz/barter-sdk
Version:
Node.js client for the Barter protocol
852 lines (841 loc) • 31.5 kB
JavaScript
import { parseIdlErrors, Program, AnchorProvider, Wallet, translateError } from '@coral-xyz/anchor';
import { getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, NATIVE_MINT, createSyncNativeInstruction } from '@solana/spl-token';
import { PublicKey, Transaction, SystemProgram } from '@solana/web3.js';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(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());
});
}
function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}
function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
}
const IDL = {
version: "0.1.2",
name: "barter",
constants: [
{
name: "MAX_FREE_ITEMS",
type: {
defined: "usize",
},
value: "3",
},
{
name: "ITEM_LAMPORT_FEE_AMOUNT",
type: "u64",
value: "2039280",
},
],
instructions: [
{
name: "cancel",
docs: [
"Invoked by the initiater of the barter agreement prior to the second party finalizing",
"their offer to cancel and close the agreement and revoke the token account delegations.",
],
accounts: [
{
name: "agreement",
isMut: true,
isSigner: false,
relations: ["initiater"],
},
{
name: "delegate",
isMut: false,
isSigner: false,
},
{
name: "initiater",
isMut: true,
isSigner: true,
},
{
name: "tokenProgram",
isMut: false,
isSigner: false,
},
],
args: [],
},
{
name: "create",
docs: [
"Creates a barter agreement program account and assigns the appropriate delegation on the",
"token accounts of the initiater's barter item offer.",
],
accounts: [
{
name: "agreement",
isMut: true,
isSigner: false,
},
{
name: "delegate",
isMut: true,
isSigner: false,
pda: {
seeds: [
{
kind: "const",
type: "string",
value: "delegate",
},
{
kind: "account",
type: "publicKey",
path: "authority",
},
],
},
},
{
name: "participant",
isMut: false,
isSigner: false,
},
{
name: "authority",
isMut: true,
isSigner: true,
},
{
name: "systemProgram",
isMut: false,
isSigner: false,
},
{
name: "tokenProgram",
isMut: false,
isSigner: false,
},
],
args: [
{
name: "amounts",
type: {
vec: "u64",
},
},
{
name: "expecting",
type: {
vec: {
defined: "BarterItem",
},
},
},
],
},
{
name: "finalize",
docs: [
"Invoked by the second party of the barter agreement after its initial creation in order",
"for them to finalize the second half of the agreement and lock it in for automated settlement.",
],
accounts: [
{
name: "agreement",
isMut: true,
isSigner: false,
relations: ["initiater"],
},
{
name: "delegate",
isMut: true,
isSigner: false,
pda: {
seeds: [
{
kind: "const",
type: "string",
value: "delegate",
},
{
kind: "account",
type: "publicKey",
path: "authority",
},
],
},
},
{
name: "initiater",
isMut: false,
isSigner: false,
},
{
name: "authority",
isMut: true,
isSigner: true,
},
{
name: "systemProgram",
isMut: false,
isSigner: false,
},
{
name: "tokenProgram",
isMut: false,
isSigner: false,
},
],
args: [
{
name: "amounts",
type: {
vec: "u64",
},
},
{
name: "expecting",
type: {
vec: {
defined: "BarterItem",
},
},
},
],
},
{
name: "settle",
docs: [
"Invoked by an asynchronous third-party to settle and transfer the barter items proposed",
"by the two parties partaking in the barter agreement.",
],
accounts: [
{
name: "agreement",
isMut: true,
isSigner: false,
relations: ["initiater", "participant"],
},
{
name: "initiaterDelegate",
isMut: false,
isSigner: false,
},
{
name: "initiater",
isMut: true,
isSigner: false,
},
{
name: "participantDelegate",
isMut: false,
isSigner: false,
},
{
name: "participant",
isMut: true,
isSigner: false,
},
{
name: "authority",
isMut: false,
isSigner: true,
},
{
name: "tokenProgram",
isMut: false,
isSigner: false,
},
],
args: [],
returns: "bool",
},
],
accounts: [
{
name: "agreement",
docs: [
"The program account state struct for a barter agreement to hold the required",
"data to ensure an agreement is secure and is fully processed.",
],
type: {
kind: "struct",
fields: [
{
name: "bump",
type: {
array: ["u8", 1],
},
},
{
name: "finalized",
type: "bool",
},
{
name: "initiater",
type: "publicKey",
},
{
name: "participant",
type: "publicKey",
},
{
name: "offer",
type: {
vec: {
defined: "BarterItemStatus",
},
},
},
{
name: "expecting",
type: {
vec: {
defined: "BarterItemStatus",
},
},
},
],
},
},
{
name: "delegate",
type: {
kind: "struct",
fields: [
{
name: "authority",
type: "publicKey",
},
{
name: "bump",
type: {
array: ["u8", 1],
},
},
],
},
},
],
types: [
{
name: "BarterItem",
docs: ["A standard barter item that can be proposed by either part of an agreement."],
type: {
kind: "struct",
fields: [
{
name: "amount",
type: "u64",
},
{
name: "mint",
type: "publicKey",
},
],
},
},
{
name: "BarterItemStatus",
docs: [
"A superset of the base `BarterItem` struct to add a boolean flag",
"to mark in state whether the item has been settled.",
],
type: {
kind: "struct",
fields: [
{
name: "item",
type: {
defined: "BarterItem",
},
},
{
name: "settled",
type: "bool",
},
],
},
},
],
events: [
{
name: "AgreementCanceled",
fields: [
{
name: "pubkey",
type: "publicKey",
index: false,
},
],
},
{
name: "AgreementCreated",
fields: [
{
name: "pubkey",
type: "publicKey",
index: false,
},
],
},
{
name: "AgreementFinalized",
fields: [
{
name: "agreement",
type: "publicKey",
index: false,
},
],
},
{
name: "AgreementSettled",
fields: [
{
name: "pubkey",
type: "publicKey",
index: false,
},
],
},
],
errors: [
{
code: 6000,
name: "CancelingAfterFinalized",
msg: "Attempting to cancel an agreement that is already finalized",
},
{
code: 6001,
name: "DelegateAuthorityMismatch",
msg: "The authority is not associated with the delegate program account",
},
{
code: 6002,
name: "InsufficientItemsAvailable",
msg: "The item amount in the proposal is higher than the available balance",
},
{
code: 6003,
name: "ItemAccountsOppositeOwnershipFailure",
msg: "The source and destination token accounts during settlement do not have opposite valid owners",
},
{
code: 6004,
name: "ItemAlreadySettled",
msg: "A barter item was submitted for settlement after already being settled",
},
{
code: 6005,
name: "ItemDelegateMismatch",
msg: "A provided item has a delegate that is not the active barter agreement",
},
{
code: 6006,
name: "ItemForAccountNotFound",
msg: "No agreement item found that matches the bounds of the token account",
},
{
code: 6007,
name: "ItemInvalidDelegation",
msg: "A provided item has no active delegate or incorrect delegated amount",
},
{
code: 6008,
name: "ItemsLengthMismatch",
msg: "The number of token amounts does not match the number of token mints",
},
{
code: 6009,
name: "ItemsMismatch",
msg: "The asset accounts provided to not match the current state's accounts",
},
{
code: 6010,
name: "ItemOwnershipMismatch",
msg: "The owner of the barter item account does not match the signer or delegate",
},
{
code: 6011,
name: "ProposingAfterFinalized",
msg: "Proposal made on an agreement that is already finalized",
},
{
code: 6012,
name: "SettlementAuthorityMismatch",
msg: "The account signing for an agreement settlement is not authorized",
},
{
code: 6013,
name: "SettlingUnfinalized",
msg: "Attempting to settle a barter agreement that has not been finalized",
},
],
};
/*
* 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/>.
*/
const MAX_SETTLEMENT_ITEMS = 6; // FIXME:
const PROGRAM_ID = new 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 Transaction().add(ix);
});
}
/**
* 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 = 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>}
*/
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>}
*/
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>}
*/
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>}
*/
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>}
*/
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[]>}
*/
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]}
*/
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]}
*/
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;
}
/*
* 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 _Barter_program, _Barter_provider;
const idlErrors = parseIdlErrors(IDL);
class Barter {
constructor(provider) {
_Barter_program.set(this, void 0);
_Barter_provider.set(this, void 0);
if (!provider.publicKey) {
throw new Error("no public key found on the argued provider");
}
else if (!provider.sendAndConfirm) {
throw new Error("no sendAndConfirm function found on the argued provider");
}
__classPrivateFieldSet(this, _Barter_program, new Program(IDL, PROGRAM_ID, provider), "f");
__classPrivateFieldSet(this, _Barter_provider, provider, "f");
}
static fromKeypair(keypair, connection) {
return new Barter(new AnchorProvider(connection, new Wallet(keypair), {}));
}
get program() {
return __classPrivateFieldGet(this, _Barter_program, "f");
}
get provider() {
return __classPrivateFieldGet(this, _Barter_provider, "f");
}
cancel(opts) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield createCancelTransaction(__classPrivateFieldGet(this, _Barter_program, "f"), opts);
return this._withParsedTransactionError(tx);
});
}
create(opts) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield createCreateTransaction(__classPrivateFieldGet(this, _Barter_program, "f"), opts);
return this._withParsedTransactionError(tx);
});
}
finalize(opts) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield createFinalizeTransaction(__classPrivateFieldGet(this, _Barter_program, "f"), opts);
return this._withParsedTransactionError(tx);
});
}
settle(opts) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield createSettleTransaction(__classPrivateFieldGet(this, _Barter_program, "f"), opts);
return this._withParsedTransactionError(tx);
});
}
_withParsedTransactionError(tx) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield __classPrivateFieldGet(this, _Barter_provider, "f").sendAndConfirm(tx);
}
catch (err) {
throw translateError(err, idlErrors);
}
});
}
}
_Barter_program = new WeakMap(), _Barter_provider = new WeakMap();
export { Barter, IDL, PROGRAM_ID, createCancelInstruction, createCancelTransaction, createCreateInstruction, createCreateTransaction, createFinalizeInstruction, createFinalizeTransaction, createSettleInstructions, createSettleTransaction, deriveAgreementAddress, deriveDelegateAddress };
//# sourceMappingURL=index.js.map