@tiplink/api
Version:
Api for creating and sending TipLinks
242 lines (241 loc) • 11.5 kB
JavaScript
"use strict";
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.EscrowTipLink = exports.getEscrowReceiverTipLink = void 0;
const web3_js_1 = require("@solana/web3.js");
const spl_token_1 = require("@solana/spl-token");
const anchor_1 = require("@coral-xyz/anchor");
const tiplink_escrow_1 = require("./anchor-generated/types/tiplink_escrow");
const constants_1 = require("./constants");
const enclave_1 = require("../enclave");
function getEscrowReceiverTipLink(connection, pda) {
return __awaiter(this, void 0, void 0, function* () {
const escrowProgram = new anchor_1.Program(tiplink_escrow_1.IDL, constants_1.ESCROW_PROGRAM_ID, { connection } // Provider interface only requires a connection, not a wallet
);
let pdaAccount;
// TODO: Implement better method of deciphering between lamport and SPL PDAs
try {
// First see if it's a lamport escrow
pdaAccount = yield escrowProgram.account.escrowLamports.fetch(pda);
}
catch (_a) {
try {
// If not, see if it's a SPL escrow
pdaAccount = yield escrowProgram.account.escrowSpl.fetch(pda);
}
catch (_b) {
// No escrow exists for this PDA
// TODO: Provide info on whether it was withdrawn or never existed
return undefined;
}
}
return pdaAccount.tiplink;
});
}
exports.getEscrowReceiverTipLink = getEscrowReceiverTipLink;
/**
* Represents an on-chain escrow that can be withdrawn by the original depositor or a TipLink, e-mailed to a recipient.
* The depositor does not see the TipLink, enabling multi-sig. (Otherwise, one signer could unilaterally withdraw the funds to themselves.)
*
* @remarks EscrowTipLinks currently only support SOL and SPL assets.
*/
class EscrowTipLink {
get depositorUrl() {
// Sanity check; error checking occurs in the enclave and on-chain program
if (!this.pda) {
throw new Error("Attempted to get depositorUrl from a non-deposited escrow.");
}
const url = new URL(constants_1.DEPOSIT_URL_BASE);
url.searchParams.append("pda", this.pda.toString());
return url;
}
constructor(toEmail, receiverTipLink, amount, depositor, escrowId, pda, mint, depositorTa) {
this.toEmail = toEmail;
this.receiverTipLink = receiverTipLink;
this.amount = amount;
this.depositor = depositor;
this.escrowId = escrowId;
this.pda = pda;
this.mint = mint;
this.depositorTa = depositorTa;
}
/**
* Creates an EscrowTipLink instance to be deposited.
*
* @param depositorTa - Overrides for non-ATA cases
*/
static create(args) {
return __awaiter(this, void 0, void 0, function* () {
const { connection, amount, toEmail, depositor, mint, depositorTa, allowDepositorOffCurve, } = args;
let { receiverTipLink } = args;
const { apiKey } = args;
if (!receiverTipLink) {
receiverTipLink = yield (0, enclave_1.createReceiverTipLink)(apiKey, toEmail);
}
const tiplinkEscrowProgram = new anchor_1.Program(tiplink_escrow_1.IDL, constants_1.ESCROW_PROGRAM_ID, { connection } // Provider interface only requires a connection, not a wallet
);
const escrowKeypair = web3_js_1.Keypair.generate();
const escrowId = escrowKeypair.publicKey;
const [pda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.PDA_SEED), escrowId.toBuffer(), depositor.toBuffer()], tiplinkEscrowProgram.programId);
let dTa = depositorTa;
if (!dTa && mint) {
dTa = yield (0, spl_token_1.getAssociatedTokenAddress)(mint.address, depositor, !!allowDepositorOffCurve);
}
return new EscrowTipLink(toEmail, receiverTipLink, amount, depositor, escrowId, pda, mint, dTa);
});
}
/**
* Creates an EscrowTipLink instance from a deposited, on-chain escrow.
*
* To get data for withdrawn / closed escrows, use `parseEscrowIx`,
* `parseEscrowTx`, or `getAllRecordedEscrowActions`, the last of which should be called
* on backends and cached for performance.
*/
static get(args) {
return __awaiter(this, void 0, void 0, function* () {
const { connection, pda } = args;
let { receiverEmail } = args;
const { apiKey } = args;
const escrowProgram = new anchor_1.Program(tiplink_escrow_1.IDL, constants_1.ESCROW_PROGRAM_ID, { connection } // Provider interface only requires a connection, not a wallet
);
let pdaAccount;
let mint;
// TODO: Implement better method of deciphering between lamport and SPL PDAs
try {
// First see if it's a lamport escrow
pdaAccount = yield escrowProgram.account.escrowLamports.fetch(pda);
}
catch (_a) {
try {
// If not, see if it's a SPL escrow
pdaAccount = yield escrowProgram.account.escrowSpl.fetch(pda);
const mintPublicKey = pdaAccount.mint;
mint = yield (0, spl_token_1.getMint)(connection, mintPublicKey);
}
catch (_b) {
// No escrow exists for this PDA
// TODO: Provide info on whether it was withdrawn or never existed
return undefined;
}
}
const receiverTipLink = pdaAccount.tiplink;
// Empty string can be passed in for withdraw without email
if (receiverEmail === undefined) {
receiverEmail = yield (0, enclave_1.getReceiverEmail)(apiKey, receiverTipLink);
}
// NOTE: We aren't able to deterministically get depositor TA from here
// because it might have been overriden and not ATA. The chain history
// has it if needed.
return new EscrowTipLink(receiverEmail, receiverTipLink, pdaAccount.amount.toNumber(), pdaAccount.depositor, pdaAccount.escrowId, pda, mint);
});
}
depositLamportTx(tiplinkEscrowProgram, escrowId, pda) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield tiplinkEscrowProgram.methods
.initializeLamport(new anchor_1.BN(this.amount.toString()), escrowId)
.accounts({
depositor: this.depositor,
pda,
treasury: constants_1.TREASURY_PUBLIC_KEY,
tiplink: this.receiverTipLink,
})
.transaction();
return tx;
});
}
depositSplTx(tiplinkEscrowProgram, escrowId, pda) {
return __awaiter(this, void 0, void 0, function* () {
// Sanity check; error checking occurs in the enclave and on-chain program
if (!this.mint) {
throw new Error("Attempted to deposit SPL without mint set");
}
if (!this.depositorTa) {
throw new Error("Attempted to deposit SPL without depositorTa set");
}
const pdaAta = yield (0, spl_token_1.getAssociatedTokenAddress)(this.mint.address, pda, true);
const tx = yield tiplinkEscrowProgram.methods
.initializeSpl(new anchor_1.BN(this.amount.toString()), escrowId)
.accounts({
depositor: this.depositor,
depositorTa: this.depositorTa,
pda,
pdaAta,
tiplink: this.receiverTipLink,
treasury: constants_1.TREASURY_PUBLIC_KEY,
mint: this.mint.address,
})
.transaction();
return tx;
});
}
depositTx(connection) {
return __awaiter(this, void 0, void 0, function* () {
const tiplinkEscrowProgram = new anchor_1.Program(tiplink_escrow_1.IDL, constants_1.ESCROW_PROGRAM_ID, { connection } // Provider interface only requires a connection, not a wallet
);
const tx = this.mint
? yield this.depositSplTx(tiplinkEscrowProgram, this.escrowId, this.pda)
: yield this.depositLamportTx(tiplinkEscrowProgram, this.escrowId, this.pda);
return tx;
});
}
withdrawLamportTx(tiplinkEscrowProgram, authority, dest) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield tiplinkEscrowProgram.methods
.withdrawLamport()
.accounts({
authority,
destination: dest,
pda: this.pda,
})
.transaction();
return tx;
});
}
withdrawSplTx(tiplinkEscrowProgram, authority, dest, allowDestOffCurve = false) {
return __awaiter(this, void 0, void 0, function* () {
// Sanity check; error checking occurs in the enclave and on-chain program
if (!this.mint) {
throw new Error("Attempted to withdraw SPL without mint set");
}
// Recalculating to keep class state smaller
const pdaAta = yield (0, spl_token_1.getAssociatedTokenAddress)(this.mint.address, this.pda, true);
// TODO: Support non-ATA
const destAta = yield (0, spl_token_1.getAssociatedTokenAddress)(this.mint.address, dest, allowDestOffCurve);
const tx = yield tiplinkEscrowProgram.methods
.withdrawSpl()
.accounts({
authority,
destination: dest,
destinationAta: destAta,
pda: this.pda,
pdaAta,
mint: this.mint.address,
})
.transaction();
return tx;
});
}
/**
* @param dest - The owner account, *not* the token account (if an SPL escrow)
* @param allowDestOffCurve - Allow calculated ATA to be off-curve (if an SPL escrow)
*/
withdrawTx(connection, authority, dest, allowDestOffCurve = false) {
return __awaiter(this, void 0, void 0, function* () {
const tiplinkEscrowProgram = new anchor_1.Program(tiplink_escrow_1.IDL, constants_1.ESCROW_PROGRAM_ID, { connection } // Provider interface only requires a connection, not a wallet
);
if (this.mint) {
return this.withdrawSplTx(tiplinkEscrowProgram, authority, dest, allowDestOffCurve);
}
return this.withdrawLamportTx(tiplinkEscrowProgram, authority, dest);
});
}
}
exports.EscrowTipLink = EscrowTipLink;