Skip to content

Commit

Permalink
add base64 helpers (#43)
Browse files Browse the repository at this point in the history
* refactor: removed redundant code

* feat: added partially signing with for base64

* test: test

* refactor: sign
  • Loading branch information
nickfrosty authored Feb 21, 2025
1 parent 18a8eec commit b9491e4
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-buttons-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gill": minor
---

added transactionToBase64WithSigners to sign and base64 encode
52 changes: 47 additions & 5 deletions packages/gill/src/__tests__/base64-transactions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import assert from "node:assert";

import { blockhash } from "@solana/rpc-types";

import { address } from "@solana/addresses";
import { createTransaction, transactionToBase64 } from "../core";
import { createKeyPairSignerFromPrivateKeyBytes, KeyPairSigner } from "@solana/signers";
import { createTransaction, transactionToBase64, transactionToBase64WithSigners } from "../core";

// initialize a sample transaction
const tx = createTransaction({
Expand All @@ -16,11 +14,55 @@ const tx = createTransaction({
},
});

// Corresponds to address `2xRiSnKRWfFwwtkBPewQ6E4QA2SK9kzypVukLh35hiS8`
const MOCK_PRIVATE_KEY_BYTES = new Uint8Array([
0xeb, 0xfa, 0x65, 0xeb, 0x93, 0xdc, 0x79, 0x15, 0x7a, 0xba, 0xde, 0xa2, 0xf7, 0x94, 0x37, 0x9d,
0xfc, 0x07, 0x1d, 0x68, 0x86, 0x87, 0x37, 0x6d, 0xc5, 0xd5, 0xa0, 0x54, 0x12, 0x1d, 0x34, 0x4a,
]);

describe("transactionToBase64", () => {
test("can base64 encode an unsigned transaction", () => {
const expected =
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABC7YxPJkVXZH3qqq8Nq1nwYa5Pm6+M9ZeObND0CCtBLXjfKbGfbEEIU1AEH81ttgpyiNLO+xurYCsjdCVcfR4YQA=";

assert.equal(expected, transactionToBase64(tx));
const result = transactionToBase64(tx);

expect(result).toBe(expected);
});
});

describe("transactionToBase64WithSigners", () => {
let mockSigner: KeyPairSigner;

beforeAll(async () => {
mockSigner = await createKeyPairSignerFromPrivateKeyBytes(MOCK_PRIVATE_KEY_BYTES);
});

test("can base64 encode a transaction without signers", async () => {
const expected =
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABC7YxPJkVXZH3qqq8Nq1nwYa5Pm6+M9ZeObND0CCtBLXjfKbGfbEEIU1AEH81ttgpyiNLO+xurYCsjdCVcfR4YQA=";

const result = await transactionToBase64WithSigners(tx);

expect(result).toBe(expected);
});

test("can base64 encode a transaction with signer", async () => {
const expected =
"Ace42d/o4XA3NGfL6hslysKyc8kB0ILDUT6diotxWdxP1cdt+oNWGztxEPb5t0F797swnV7NLCguh94nGqetQwABAAABHQ6Thk3MgV/D8oYYCRHQCj/SBt4xoclCh8tD8F/J8rXjfKbGfbEEIU1AEH81ttgpyiNLO+xurYCsjdCVcfR4YQA=";

const tx = createTransaction({
version: "legacy",
feePayer: mockSigner,
instructions: [],
latestBlockhash: {
blockhash: blockhash("GK1nopeF3P8J46dGqq4KfaEWopZU7K65F6CKQXuUdr3z"),
lastValidBlockHeight: 0n,
},
});

const result = await transactionToBase64WithSigners(tx);

expect(result).toBe(expected);
});
});
33 changes: 24 additions & 9 deletions packages/gill/src/core/base64-transactions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import { pipe } from "@solana/functional";
import { CompilableTransactionMessage } from "@solana/transaction-messages";
import { partiallySignTransactionMessageWithSigners } from "@solana/signers";
import type { CompilableTransactionMessage } from "@solana/transaction-messages";

import {
Transaction,
type Transaction,
compileTransaction,
Base64EncodedWireTransaction,
type Base64EncodedWireTransaction,
getBase64EncodedWireTransaction,
} from "@solana/transactions";

/**
* Compile a Transaction to a base64 string
* Compile a transaction to a base64 string
*
* Note: This will NOT attempt to sign the transaction,
* so it will be missing `signatures` from any of the attached Signers
*
* Use {@link transactionToBase64WithSignatures} sign and base64 encode
*/
export function transactionToBase64(
tx: CompilableTransactionMessage | Transaction,
): Base64EncodedWireTransaction {
if ("messageBytes" in tx) {
return pipe(tx, getBase64EncodedWireTransaction);
} else {
return pipe(tx, compileTransaction, getBase64EncodedWireTransaction);
}
if ("messageBytes" in tx) return pipe(tx, getBase64EncodedWireTransaction);
else return pipe(tx, compileTransaction, getBase64EncodedWireTransaction);
}

/**
* Compile a transaction to a base64 string and sign it with all attached Signers
*
* See also {@link partiallySignTransactionMessageWithSigners}
*/
export async function transactionToBase64WithSigners(
tx: CompilableTransactionMessage | Transaction,
): Promise<Base64EncodedWireTransaction> {
if ("messageBytes" in tx) return transactionToBase64(tx);
else return transactionToBase64(await partiallySignTransactionMessageWithSigners(tx));
}
12 changes: 9 additions & 3 deletions packages/gill/src/core/prepare-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
import type { GetLatestBlockhashApi, Rpc, SimulateTransactionApi } from "@solana/rpc";
import { isSetComputeLimitInstruction } from "../programs/compute-budget";
import { getComputeUnitEstimateForTransactionMessageFactory } from "../kit";
import { debug } from "./debug";
import { transactionToBase64 } from "./base64-transactions";
import { debug, isDebugEnabled } from "./debug";
import { transactionToBase64WithSigners } from "./base64-transactions";

type PrepareCompilableTransactionMessage =
| CompilableTransactionMessage
Expand Down Expand Up @@ -121,7 +121,13 @@ export async function prepareTransaction<TMessage extends PrepareCompilableTrans

assertIsTransactionMessageWithBlockhashLifetime(config.transaction);

debug(`Transaction as base64: ${transactionToBase64(config.transaction)}`, "debug");
// skip the async call if debugging is off
if (isDebugEnabled()) {
debug(
`Transaction as base64: ${await transactionToBase64WithSigners(config.transaction)}`,
"debug",
);
}

return config.transaction;
}
4 changes: 2 additions & 2 deletions packages/gill/src/kit/send-transaction-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TransactionWithBlockhashLifetime,
TransactionWithDurableNonceLifetime,
} from '@solana/transactions';
import { debug, getExplorerLink, transactionToBase64 } from '../core';
import { debug, getExplorerLink } from '../core';

interface SendAndConfirmDurableNonceTransactionConfig
extends SendTransactionBaseConfig,
Expand Down Expand Up @@ -82,8 +82,8 @@ export async function sendTransaction_INTERNAL_ONLY_DO_NOT_EXPORT({
...sendTransactionConfig
}: SendTransactionBaseConfig): Promise<Signature> {
debug(`Sending transaction: ${getExplorerLink({transaction: getSignatureFromTransaction(transaction)})}`)
debug(`Transaction as base64: ${transactionToBase64(transaction)}`, "debug");
const base64EncodedWireTransaction = getBase64EncodedWireTransaction(transaction);
debug(`Transaction as base64: ${base64EncodedWireTransaction}`, "debug");
return await rpc
.sendTransaction(base64EncodedWireTransaction, {
...getSendTransactionConfigWithAdjustedPreflightCommitment(commitment, sendTransactionConfig),
Expand Down

0 comments on commit b9491e4

Please sign in to comment.