signer/delegation.mjs
v0.1.0 · 3.3 KB
/** 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,
},
};
}