Skip to content
markpaper

signer/delegation.mjs

v0.1.0 · 3.3 KB

Download file
/** Optional unsigned delegation builder. This function neither signs nor sends a transaction. */

import { buildDelegateTraderIx, getPhoenixTraderSubaccountAddress } from '@ellipsis-labs/rise';
import {
  address,
  appendTransactionMessageInstruction,
  compileTransaction,
  createTransactionMessage,
  getBase64EncodedWireTransaction,
  pipe,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
} from '@solana/kit';
import { GLOBAL_CONFIGURATION, LOG_AUTHORITY, PROGRAM_ADDRESS, parseTraderHeader, pubkeyOrNull } from './sidecar.mjs';

export async function buildDelegateTraderTx({ authority, action, delegate, rpc }) {
  if (!pubkeyOrNull(authority)) throw new TypeError('Invalid owner public key');
  const target = action === 'revoke' ? authority : action === 'delegate' ? delegate : null;
  if (!pubkeyOrNull(target)) throw new TypeError('Action must be delegate or revoke with a valid delegate public key');
  if (action === 'delegate' && target === authority) throw new TypeError('Delegate must differ from owner');
  const pda = String(
    await getPhoenixTraderSubaccountAddress({
      authority: address(authority),
      traderPdaIndex: 0,
      subaccountIndex: 0,
      phoenixProgramAddress: address(PROGRAM_ADDRESS),
    }),
  );
  const acc = await rpc.getAccountInfo(address(pda), { encoding: 'base64', commitment: 'confirmed' }).send();
  if (!acc.value) throw new Error('Trader account not found');
  const header = parseTraderHeader(new Uint8Array(Buffer.from(acc.value.data[0], 'base64')), String(acc.value.owner));
  if (!header.ok) throw new Error(header.why);
  if (header.authority !== authority || header.pdaIndex !== 0 || header.subaccountIndex !== 0)
    throw new Error('Account owner or indices mismatch');
  if (header.positionAuthority === target) throw new Error('Delegation already has requested target');
  const ix = buildDelegateTraderIx({
    programAddress: address(PROGRAM_ADDRESS),
    logAuthorityAddress: address(LOG_AUTHORITY),
    globalConfigurationAddress: address(GLOBAL_CONFIGURATION),
    traderWallet: address(authority),
    traderAccount: address(pda),
    newPositionAuthority: address(target),
  });
  const { value: bh } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
  const msg = pipe(
    createTransactionMessage({ version: 0 }),
    (m) => setTransactionMessageFeePayer(address(authority), m),
    (m) => setTransactionMessageLifetimeUsingBlockhash(bh, m),
    (m) => appendTransactionMessageInstruction(ix, m),
  );
  const tx = getBase64EncodedWireTransaction(compileTransaction(msg));
  const sim = await rpc
    .simulateTransaction(tx, {
      encoding: 'base64',
      sigVerify: false,
      replaceRecentBlockhash: true,
      commitment: 'confirmed',
    })
    .send();
  return {
    action,
    authority,
    pda,
    newPositionAuthority: target,
    tx,
    lastValidBlockHeight: String(bh.lastValidBlockHeight),
    instruction: {
      program: String(ix.programAddress),
      accounts: ix.accounts.map((a) => ({ address: String(a.address), role: a.role })),
      dataHex: Buffer.from(ix.data).toString('hex'),
    },
    simulation: {
      err: sim.value.err ?? null,
      logs: sim.value.logs ?? [],
      unitsConsumed: sim.value.unitsConsumed != null ? String(sim.value.unitsConsumed) : null,
    },
  };
}
All files