Skip to content

Commit 391c9da

Browse files
authored
accept base 64 input for solana tx (#59)
1 parent 0a1f9c5 commit 391c9da

3 files changed

Lines changed: 49 additions & 9 deletions

File tree

apps/solana/src/parser.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
type StakeInstructionParams,
2020
type SystemInstructionParams,
2121
} from '@/types';
22+
import { base64ToHex, isBase64, isHex } from '@/utils';
2223

2324
export type MessageLike = {
2425
header: {
@@ -31,8 +32,6 @@ export type MessageLike = {
3132
instructions: { programIdIndex: number; accounts: number[]; data: string }[];
3233
};
3334

34-
const isHex = (s: string) => /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0;
35-
3635
export const looksLikeMessage = (obj: unknown): obj is MessageLike => {
3736
const msg = obj as Record<string, unknown>;
3837
return !!msg.header && !!msg.recentBlockhash && !!msg.accountKeys && !!msg.instructions;
@@ -321,16 +320,26 @@ export const parseSolTx = (txRaw: string): ParseSolTxResult => {
321320
};
322321
}
323322
} catch {
324-
// fall through to hex path
323+
// fall through to hex/base64 path
324+
}
325+
}
326+
327+
// Check if input is base64, if so convert to hex
328+
let hexInput = input;
329+
if (!isHex(input) && isBase64(input)) {
330+
try {
331+
hexInput = base64ToHex(input);
332+
} catch (error) {
333+
throw new Error('Failed to decode base64 input');
325334
}
326335
}
327336

328-
if (!isHex(input)) {
329-
throw new Error('Input is not valid hex or Fireblocks message JSON');
337+
if (!isHex(hexInput)) {
338+
throw new Error('Input is not valid hex, base64, or Fireblocks message JSON');
330339
}
331340

332341
try {
333-
const buffer = Buffer.from(input, 'hex');
342+
const buffer = Buffer.from(hexInput, 'hex');
334343

335344
const tx = Transaction.from(buffer);
336345
const parsedInstructions = tx.instructions.map(safeDecode);

apps/solana/src/solana-adapter.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type ProtocolAdapter, SOL } from '@protocols/shared';
22
import { Summary } from '@/components/Summary';
33
import { convertToMessage, looksLikeMessage, type MessageLike, type ParseSolTxResult, parseSolTx } from '@/parser';
44
import type { DecodedInstruction } from '@/types';
5+
import { base64ToHex, isBase64, isHex } from '@/utils';
56

67
const computeSolanaHash = async (rawTx: string): Promise<string> => {
78
try {
@@ -27,7 +28,12 @@ const computeSolanaHash = async (rawTx: string): Promise<string> => {
2728
} catch {}
2829
}
2930

30-
const transactionUint8Array = new Uint8Array(input.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || []);
31+
let hexInput = input;
32+
if (!isHex(input) && isBase64(input)) {
33+
hexInput = base64ToHex(input);
34+
}
35+
36+
const transactionUint8Array = new Uint8Array(hexInput.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || []);
3137
const hashBuffer = await crypto.subtle.digest('SHA-256', transactionUint8Array);
3238
const hashArray = Array.from(new Uint8Array(hashBuffer));
3339
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
@@ -40,16 +46,23 @@ const isValidSolanaInput = (rawTx: string): boolean => {
4046
const input = rawTx.trim();
4147
if (!input) return false;
4248

49+
// JSON
4350
if (input.startsWith('{') || input.startsWith('[')) return true;
4451

45-
return /^[0-9a-fA-F]+$/.test(input) && input.length % 2 === 0;
52+
// Hex
53+
if (isHex(input)) return true;
54+
55+
// Base64
56+
if (isBase64(input)) return true;
57+
58+
return false;
4659
};
4760

4861
export const solanaAdapter: ProtocolAdapter<ParseSolTxResult> = {
4962
protocol: SOL,
5063
name: 'solana',
5164
displayName: 'Solana',
52-
placeholder: 'Paste your transaction as hex or Fireblocks message JSON',
65+
placeholder: 'Paste your transaction as hex, base64, or Fireblocks message JSON',
5366

5467
validateInput: isValidSolanaInput,
5568
parseTransaction: async (rawTx) => parseSolTx(rawTx),

apps/solana/src/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
11
export const solExplorerLink = (hash: string, type: 'tx' | 'address') => {
22
return `https://solscan.io/${type}/${hash}`;
33
};
4+
5+
export const isHex = (s: string): boolean => {
6+
return /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0;
7+
};
8+
9+
export const isBase64 = (s: string): boolean => {
10+
return /^[A-Za-z0-9+/]+={0,2}$/.test(s) && s.length % 4 === 0;
11+
};
12+
13+
export const base64ToHex = (base64: string): string => {
14+
const binary = atob(base64);
15+
let hex = '';
16+
for (let i = 0; i < binary.length; i++) {
17+
const byte = binary.charCodeAt(i).toString(16).padStart(2, '0');
18+
hex += byte;
19+
}
20+
return hex;
21+
};

0 commit comments

Comments
 (0)