UNPKG

@tiplink/api

Version:

Api for creating and sending TipLinks

242 lines (241 loc) 11.5 kB
"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;