UNPKG

solana-options

Version:

Minting of options contract NFTs on the Solana blockchain

473 lines (472 loc) 22.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.close_option = exports.exercise_option = exports.exercise_put = exports.exercise_call = exports.create_option = exports.create_new_nft_mint = exports.create_put = exports.create_call = exports.OptionType = exports.create_doc_img = exports.publish_doc = exports.print_contract = exports.verify_contract = exports.get_contract_from_blockchain = void 0; const spl_token_1 = require("@solana/spl-token"); const web3_js_1 = require("@solana/web3.js"); const web3_js_2 = require("@solana/web3.js"); const bn_js_1 = __importDefault(require("bn.js")); const dayjs_1 = __importDefault(require("dayjs")); const layout_1 = require("./layout"); const utils_1 = require("./utils"); Object.defineProperty(exports, "print_contract", { enumerable: true, get: function () { return utils_1.print_contract; } }); Object.defineProperty(exports, "get_contract_from_blockchain", { enumerable: true, get: function () { return utils_1.get_contract_from_blockchain; } }); Object.defineProperty(exports, "verify_contract", { enumerable: true, get: function () { return utils_1.verify_contract; } }); const doc_1 = require("./doc"); Object.defineProperty(exports, "publish_doc", { enumerable: true, get: function () { return doc_1.publish_doc; } }); Object.defineProperty(exports, "create_doc_img", { enumerable: true, get: function () { return doc_1.create_doc_img; } }); const spl_token_registry_1 = require("@solana/spl-token-registry"); const OPTIONS_PROGRAM_ID = process.env.OPTIONS_PROGRAM_ID || process.env.REACT_APP_OPTIONS_PROGRAM_ID || "DV4NugS55eXXposgxLnLr7WxySCTpaDd3cQPegFenHaj"; const SEED = "optionsnft"; let TOKEN_LIST = null; const CLUSTER_SLUG = "mainnet-beta"; var OptionType; (function (OptionType) { OptionType[OptionType["call"] = 0] = "call"; OptionType[OptionType["put"] = 1] = "put"; })(OptionType = exports.OptionType || (exports.OptionType = {})); var Instruction; (function (Instruction) { Instruction[Instruction["create"] = 0] = "create"; Instruction[Instruction["exercise"] = 1] = "exercise"; Instruction[Instruction["create_new_nft_mint"] = 2] = "create_new_nft_mint"; })(Instruction || (Instruction = {})); async function get_token_map() { if (TOKEN_LIST) { let symbol_to_address_map = new Map(TOKEN_LIST.map(t => [t.symbol, t.address])); return symbol_to_address_map; } else { let tokens = await new spl_token_registry_1.TokenListProvider().resolve(); const tokenList = tokens.filterByClusterSlug(CLUSTER_SLUG).getList(); TOKEN_LIST = tokenList; return get_token_map(); } } /** * * @param connection * @param strike * @param expiry * @param multiple * @param creator_account * @param instrument * @param strike_instrument * @param creator_instrument_acc * @param creator_strike_instrument_acc * @returns */ async function create_call(connection, strike, expiry, multiple, creator_account, instrument, strike_instrument, creator_instrument_acc, creator_strike_instrument_acc) { console.log("creating call contract"); return create_option(connection, strike, expiry, multiple, creator_account, instrument, strike_instrument, creator_instrument_acc, creator_strike_instrument_acc, OptionType.call); } exports.create_call = create_call; /** * * @param connection * @param strike * @param expiry * @param multiple * @param creator_account * @param instrument * @param strike_instrument * @param creator_instrument_acc * @param creator_strike_instrument_acc * @returns */ async function create_put(connection, strike, expiry, multiple, creator_account, instrument, strike_instrument, creator_instrument_acc, creator_strike_instrument_acc) { console.log("creating put contract"); return create_option(connection, strike, expiry, multiple, creator_account, instrument, strike_instrument, creator_instrument_acc, creator_strike_instrument_acc, OptionType.put); } exports.create_put = create_put; async function create_new_nft_mint(connection, multiple, creator_account) { const instrument_mint_acc = new web3_js_1.Keypair(); console.log("instrument mint account key: ", instrument_mint_acc.publicKey.toString()); const mint_rent = await connection.getMinimumBalanceForRentExemption(spl_token_1.MintLayout.span, 'confirmed'); console.log("using %s lamports to create the instrument mint account", mint_rent); const createInstrumentMintIx = web3_js_2.SystemProgram.createAccount({ programId: spl_token_1.TOKEN_PROGRAM_ID, space: spl_token_1.MintLayout.span, lamports: await connection.getMinimumBalanceForRentExemption(spl_token_1.MintLayout.span, 'confirmed'), fromPubkey: creator_account.publicKey, newAccountPubkey: instrument_mint_acc.publicKey }); // const instrument = instrument_mint_acc.publicKey // get the address for the account that will be associated with the NFT // this code is from the associated token program const [creator_instrument_acc, _] = await web3_js_2.PublicKey.findProgramAddress([ creator_account.publicKey.toBytes(), spl_token_1.TOKEN_PROGRAM_ID.toBytes(), instrument_mint_acc.publicKey.toBytes() ], spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); console.log("instrument account key: ", creator_instrument_acc.toString()); const optionsProgramId = new web3_js_2.PublicKey(OPTIONS_PROGRAM_ID); // call the program to initialize this mint. the program will be the mint authority for this mint const [pda, _bump_seed] = await web3_js_2.PublicKey.findProgramAddress([Buffer.from(SEED)], optionsProgramId); let pda_account = new web3_js_2.PublicKey(pda); const createNewNFTMintIx = new web3_js_2.TransactionInstruction({ programId: optionsProgramId, keys: [{ pubkey: creator_account.publicKey, isSigner: true, isWritable: true }, { pubkey: creator_instrument_acc, isSigner: false, isWritable: true }, { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: web3_js_2.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, { pubkey: web3_js_2.SystemProgram.programId, isSigner: false, isWritable: false }, { pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: pda_account, isSigner: false, isWritable: true }, { pubkey: instrument_mint_acc.publicKey, isSigner: false, isWritable: true } ], data: Buffer.from(Uint8Array.of(Instruction.create_new_nft_mint, ...new bn_js_1.default(multiple).toArray("le", 8))) }); console.log("sending the instructions ..."); const tx = new web3_js_2.Transaction() .add(createInstrumentMintIx, createNewNFTMintIx); if (!("secretKey" in creator_account)) { // this must be a wallet type then let wallet = creator_account; let sig = await wallet.sendTransaction(tx, connection, { signers: [instrument_mint_acc] }); return [sig, instrument_mint_acc, creator_instrument_acc]; } let sig = await connection.sendTransaction(tx, [creator_account, instrument_mint_acc], { skipPreflight: false, preflightCommitment: 'finalized' }); return [sig, instrument_mint_acc, creator_instrument_acc]; } exports.create_new_nft_mint = create_new_nft_mint; async function create_option(connection, strike, expiry, multiple, creator_account, instrument, strike_instrument, creator_instrument_acc, creator_strike_instrument_acc, kind) { // check if the either instrument or strike_instrument is a symbol or address(public); assume symbol if string if (typeof instrument == "string" || typeof strike_instrument == "string") { let symbol_to_address_map = await get_token_map(); instrument = typeof instrument == "string" ? new web3_js_2.PublicKey(symbol_to_address_map.get(instrument)) : instrument; strike_instrument = typeof strike_instrument == "string" ? new web3_js_2.PublicKey(symbol_to_address_map.get(strike_instrument)) : strike_instrument; if (!strike_instrument) throw "invalid strike instrument symbol"; } console.log("bob initiating contract"); if (instrument == null) { // In this case, create a new NFT mint then assign it as the instrument if (creator_instrument_acc != null) throw "when instrument is null, the creator_instrument_acc must be null"; //create a mint address for the new instrument console.log("creating new nft mint ..."); const [sig, instrument_mint_acc, new_creator_instrument_acc] = await create_new_nft_mint(connection, multiple, creator_account); creator_instrument_acc = new_creator_instrument_acc; instrument = instrument_mint_acc.publicKey; let res = await connection.confirmTransaction(sig, "finalized"); console.log("new nft mint result", res); console.log("done creating new nft mint"); } // create collateral account console.log("creating collateral acc"); const collateralAccount = new web3_js_1.Keypair(); console.log("collateral account key: ", collateralAccount.publicKey.toString()); const createCollateralAccIx = web3_js_2.SystemProgram.createAccount({ programId: spl_token_1.TOKEN_PROGRAM_ID, space: spl_token_1.AccountLayout.span, lamports: await connection.getMinimumBalanceForRentExemption(spl_token_1.AccountLayout.span, 'confirmed'), fromPubkey: creator_account.publicKey, newAccountPubkey: collateralAccount.publicKey }); // init collateral account let initCollateralAccountIx; if (kind == OptionType.call) { console.log("creating init call collateral acc instruction with instrument", instrument.toString()); initCollateralAccountIx = spl_token_1.Token.createInitAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, instrument, collateralAccount.publicKey, creator_account.publicKey); } else { console.log("creating init put collateral acc instruction"); initCollateralAccountIx = spl_token_1.Token.createInitAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, strike_instrument, collateralAccount.publicKey, creator_account.publicKey); } // create options trading account (it is a program account not token account) console.log("creationg options program account create instruction"); const optionsAccount = new web3_js_1.Keypair(); const optionsProgramId = new web3_js_2.PublicKey(OPTIONS_PROGRAM_ID); const createOptionsAccountIx = web3_js_2.SystemProgram.createAccount({ space: layout_1.OPTION_ACCOUNT_DATA_LAYOUT.span, lamports: await connection.getMinimumBalanceForRentExemption(layout_1.OPTION_ACCOUNT_DATA_LAYOUT.span, 'singleGossip'), fromPubkey: creator_account.publicKey, newAccountPubkey: optionsAccount.publicKey, programId: optionsProgramId }); // Create a mint address that will hold the NFT attached to this contract let nftTokenAccount = new web3_js_1.Keypair(); // get the address for the account that will be associated with the NFT // this code is from the associated token program const [nft_associated_account, _] = await web3_js_2.PublicKey.findProgramAddress([ creator_account.publicKey.toBytes(), spl_token_1.TOKEN_PROGRAM_ID.toBytes(), nftTokenAccount.publicKey.toBytes() ], spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); console.log("nft token: ", nftTokenAccount.publicKey.toString()); console.log("nft associated account: ", nft_associated_account.toString()); const [pda, _bump_seed] = await web3_js_2.PublicKey.findProgramAddress([Buffer.from(SEED)], optionsProgramId); let pda_account = new web3_js_2.PublicKey(pda); const createOptionsIx = new web3_js_2.TransactionInstruction({ programId: optionsProgramId, keys: [{ pubkey: creator_account.publicKey, isSigner: true, isWritable: true }, { pubkey: collateralAccount.publicKey, isSigner: false, isWritable: true }, { pubkey: creator_instrument_acc, isSigner: false, isWritable: true }, { pubkey: creator_strike_instrument_acc, isSigner: false, isWritable: true }, { pubkey: nftTokenAccount.publicKey, isSigner: true, isWritable: true }, { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: web3_js_2.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, { pubkey: web3_js_2.SystemProgram.programId, isSigner: false, isWritable: false }, { pubkey: nft_associated_account, isSigner: false, isWritable: true }, { pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: optionsAccount.publicKey, isSigner: false, isWritable: true }, { pubkey: pda_account, isSigner: false, isWritable: true } ], data: Buffer.from(Uint8Array.of(0, ...new bn_js_1.default(strike).toArray("le", 8), ...new bn_js_1.default(multiple).toArray("le", 8), ...new bn_js_1.default(expiry).toArray("le", 8), kind)) }); let contract = { strike: strike, expiry: expiry, multiple: multiple, instrument: instrument, strike_instrument: strike_instrument, nft_id: nftTokenAccount.publicKey, nft_account: nft_associated_account, account_id: optionsAccount.publicKey, collateral_acc: collateralAccount.publicKey, // a call means writer receives strike_instrument in exchange for instrument // a put means writer receives the instrument and send out the strike instrument writer_recv_acc: (kind == OptionType.call) ? creator_strike_instrument_acc : creator_instrument_acc, writer: creator_account.publicKey, kind: kind }; // transfer tokens to temp and then get escrow info console.log("generated contract", (0, utils_1.print_contract)(contract)); console.log("sending the instructions ..."); const tx = new web3_js_2.Transaction() .add(createCollateralAccIx, initCollateralAccountIx, createOptionsAccountIx, createOptionsIx); if (!("secretKey" in creator_account)) { // this must be a wallet type then let wallet = creator_account; let sig = await wallet.sendTransaction(tx, connection, { signers: [collateralAccount, optionsAccount, nftTokenAccount] }); console.log("done"); return [sig, contract]; } let sig = await connection.sendTransaction(tx, [creator_account, collateralAccount, optionsAccount, nftTokenAccount], { skipPreflight: false, preflightCommitment: 'finalized' }); console.log("done"); return [sig, contract]; } exports.create_option = create_option; async function exercise_call(connection, contract, buyer_acc, buyer_nft_acc, buyer_receive_acc, buyer_send_acc) { return exercise_option(connection, contract, buyer_acc, buyer_nft_acc, buyer_receive_acc, buyer_send_acc, OptionType.call); } exports.exercise_call = exercise_call; async function exercise_put(connection, contract, buyer_acc, buyer_nft_acc, buyer_receive_acc, buyer_send_acc) { return exercise_option(connection, contract, buyer_acc, buyer_nft_acc, buyer_receive_acc, buyer_send_acc, OptionType.put); } exports.exercise_put = exercise_put; /** * Exercises the options contract * @param connection connection to the cluster * @param contract the Contract * @param buyer_acc buyer's account or buyers wallet * @param buyer_nft_acc the buyer's account that holds the ownership nft. This get burned by the exercise instruction * @param buyer_receive_acc account the buyers expects to receive the options collateral * @param buyer_send_acc the account holding the tokens the buyer is sending to exercise this contract * @param kind call or put * @returns signature */ async function exercise_option(connection, contract, buyer_acc, buyer_nft_acc, buyer_receive_acc, buyer_send_acc, kind) { let strike = contract.strike; let expiry = contract.expiry; let multiple = contract.multiple; let today = (0, dayjs_1.default)(); if (today > (0, dayjs_1.default)(expiry * 1000)) { console.error("This contract exipred on %s , today is %s", (0, dayjs_1.default)(expiry).format(), today.format()); throw "contract has exipired"; } const optionsProgramId = new web3_js_2.PublicKey(OPTIONS_PROGRAM_ID); let nft_token_mint = new web3_js_2.PublicKey(contract.nft_id); let collateral_acc = new web3_js_2.PublicKey(contract.collateral_acc); let writer_recv_acc = new web3_js_2.PublicKey(contract.writer_recv_acc); let options_program_account = new web3_js_2.PublicKey(contract.account_id); const [pda, bump_seed] = await web3_js_2.PublicKey.findProgramAddress([Buffer.from(SEED)], optionsProgramId); let pda_account = new web3_js_2.PublicKey(pda); console.log("exercising contract", contract); const exerciseIx = new web3_js_2.TransactionInstruction({ programId: optionsProgramId, keys: [{ pubkey: buyer_acc.publicKey, isSigner: true, isWritable: true }, { pubkey: buyer_nft_acc, isSigner: false, isWritable: true }, { pubkey: nft_token_mint, isSigner: false, isWritable: true }, { pubkey: buyer_send_acc, isSigner: false, isWritable: true }, { pubkey: buyer_receive_acc, isSigner: false, isWritable: true }, { pubkey: collateral_acc, isSigner: false, isWritable: true }, { pubkey: writer_recv_acc, isSigner: false, isWritable: true }, { pubkey: contract.writer, isSigner: false, isWritable: true }, { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: options_program_account, isSigner: false, isWritable: true }, { pubkey: pda_account, isSigner: false, isWritable: true }, { pubkey: web3_js_2.SystemProgram.programId, isSigner: false, isWritable: false } ], data: Buffer.from(Uint8Array.of(1, ...new bn_js_1.default(strike).toArray("le", 8), ...new bn_js_1.default(multiple).toArray("le", 8), kind)) }); console.log("sending the exercise instructions ..."); const tx = new web3_js_2.Transaction() .add(exerciseIx); if (!("secretKey" in buyer_acc)) { // this must be a wallet type then let wallet = buyer_acc; return wallet.sendTransaction(tx, connection); } return connection.sendTransaction(tx, [buyer_acc], { skipPreflight: false, preflightCommitment: 'confirmed' }); } exports.exercise_option = exercise_option; /** * Creators call this to close expired contracts. This instruction returns the collateral to the creator if the contract is expired and * hasn't been exercised yet, and returns any lamport used to create the options program account back to the creator and cleans out its data. * Exercised contracts are automatically closed * @param connection Connection to cluster * @param contract Contract * @param creator_acc The creators keypair * @param creator_receive_acc the receiving account where the released collateral will be sent back * @returns signature */ async function close_option(connection, contract, creator_acc, creator_receive_acc) { const optionsProgramId = new web3_js_2.PublicKey(OPTIONS_PROGRAM_ID); let collateral_acc = new web3_js_2.PublicKey(contract.collateral_acc); let options_program_account = new web3_js_2.PublicKey(contract.account_id); const [pda, _] = await web3_js_2.PublicKey.findProgramAddress([Buffer.from(SEED)], optionsProgramId); console.log("exercising contract", contract); const exerciseIx = new web3_js_2.TransactionInstruction({ programId: optionsProgramId, keys: [{ pubkey: creator_acc.publicKey, isSigner: true, isWritable: true }, { pubkey: collateral_acc, isSigner: false, isWritable: true }, { pubkey: creator_receive_acc, isSigner: false, isWritable: true }, { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: options_program_account, isSigner: false, isWritable: true }, { pubkey: pda, isSigner: false, isWritable: true } ], data: Buffer.from(Uint8Array.of(3)) }); console.log("sending the close instructions ..."); const tx = new web3_js_2.Transaction() .add(exerciseIx); if (!("secretKey" in creator_acc)) { // this must be a wallet type then let wallet = creator_acc; return wallet.sendTransaction(tx, connection); } return connection.sendTransaction(tx, [creator_acc], { skipPreflight: false, preflightCommitment: 'confirmed' }); } exports.close_option = close_option;