@flarenetwork/smart-accounts-encoder
v0.1.2
Published
Encode and decode instructions to be used in Flare smart account
Readme
Smart accounts encoder
Smart Accounts Encoder is a TypeScript library designed to simplify the encoding and decoding of instructions used in the Flare smart accounts workflow. It provides a unified interface and strongly typed classes for constructing, encoding, and decoding instructions. This enables developers to work with human-readable data structures in their applications. The library ensures correct encoding and decoding of instructions that are sent as memo fields of XRPL Payment transactions.
Repository structure
smart-accounts-encoder
├── src # Source code for the library
│ ├── common # Shared logic and definitions
│ │ ├── types # Type definitions used across the codebase
│ │ │ └── common.types.ts # Common TypeScript types for smart account structures
│ │ └── utils # Utility functions
│ │ ├── encoding.utils.test.ts # Unit tests for encoding utilities
│ │ └── encoding.utils.ts # Encoding helper functions
│ ├── instructions # All smart account instruction definitions
│ │ ├── firelight # Firelight instructions
│ │ ├── fxrp # FXRP instructions
│ │ ├── upshift # Upshift instructions
│ │ ├── base-instruction.ts # Base class for smart account instructions
│ │ └── instructions.ts # A registry of available instructions
│ └── index.ts # Main export file for the library
├── eslint.config.mjs # ESLint configuration
├── package.json # Project metadata and dependencies
├── pnpm-lock.yaml # Lock file for reproducible installs with pnpm
├── README.md # This documentation file
├── tsconfig.build.json # TypeScript configuration for publishing to npm
└── tsconfig.json # General TypeScript configurationInstallation
npm install @flarenetwork/smart-accounts-encoder
# or
pnpm install @flarenetwork/smart-accounts-encoder
# or
yarn add @flarenetwork/smart-accounts-encoderUsage
Basic Example
The library provides classes for encoding and decoding smart account instructions. Each instruction can be created with input data, encoded to a hex string, and decoded back to an instruction instance. This allows for operating with human-readable data, while the library takes care of the encoding and decoding.
import { FXRPCollateralReservationInstruction } from "@flarenetwork/smart-accounts-encoder";
// Create an instruction
const instruction = new FXRPCollateralReservationInstruction({
walletId: 42,
value: 1n,
agentVaultId: 1,
});
// Encode to hex string
const hex = instruction.encode();
console.log(hex); // "0x002a0000000000000000010001..."
// Decode from hex string
const decoded = FXRPCollateralReservationInstruction.decode(hex);
console.log(decoded.data); // { walletId: 42, value: 1n, agentVaultId: 1 }Instruction Types
FXRP Instructions
- CollateralReservation: Reserve collateral for FXRP minting
- Transfer: Transfer FXRP tokens to another Flare account
- Redeem: Redeem FXRP tokens to XRP
import {
FXRPCollateralReservationInstruction,
FXRPTransferInstruction,
FXRPRedeemInstruction,
} from "@flarenetwork/smart-accounts-encoder";
// Collateral Reservation
const collateral = new FXRPCollateralReservationInstruction({
walletId: 42,
value: 1n,
agentVaultId: 1,
});
// Transfer
const transfer = new FXRPTransferInstruction({
walletId: 42,
value: 1n,
recipientAddress: "f5488132432118596fa13800b68df4c0ff25131d",
});
// Redeem
const redeem = new FXRPRedeemInstruction({
walletId: 42,
value: 1n,
});Firelight Instructions
- CollateralReservationAndDeposit: Reserve collateral and deposit in one operation
- Deposit: Deposit FXRP to a Firelight vault
- Redeem: Start the redeem process from a Firelight vault
- ClaimWithdraw: Claim withdrawal of FXRP from a Firelight vault
import {
FirelightCollateralReservationAndDepositInstruction,
FirelightDepositInstruction,
FirelightRedeemInstruction,
FirelightClaimWithdrawInstruction,
} from "@flarenetwork/smart-accounts-encoder";
// Collateral Reservation and Deposit
const collateralDeposit = new FirelightCollateralReservationAndDepositInstruction({
walletId: 42,
value: 1n,
agentVaultId: 1,
vaultId: 2,
});
// Deposit
const deposit = new FirelightDepositInstruction({
walletId: 42,
value: 1n,
vaultId: 2,
});
// Redeem
const redeem = new FirelightRedeemInstruction({
walletId: 42,
value: 1n,
vaultId: 2,
});
// Claim Withdraw
const claimWithdraw = new FirelightClaimWithdrawInstruction({
walletId: 42,
period: 1,
vaultId: 2,
});Upshift Instructions
- CollateralReservationAndDeposit: Reserve collateral and deposit in one operation
- Deposit: Deposit FXRP to an Upshift vault
- RequestRedeem: Start a redeem process from an Upshift vault
- Claim: Claim requested FXRP from an Upshift vault
import {
UpshiftCollateralReservationAndDepositInstruction,
UpshiftDepositInstruction,
UpshiftRequestRedeemInstruction,
UpshiftClaimInstruction,
} from "@flarenetwork/smart-accounts-encoder";
// Collateral Reservation and Deposit
const collateralDeposit = new UpshiftCollateralReservationAndDepositInstruction({
walletId: 42,
value: 1n,
agentVaultId: 1,
vaultId: 2,
});
// Deposit
const deposit = new UpshiftDepositInstruction({
walletId: 42,
value: 1n,
vaultId: 2,
});
// Request Redeem
const requestRedeem = new UpshiftRequestRedeemInstruction({
walletId: 42,
value: 1n,
vaultId: 2,
});
// Claim
const claim = new UpshiftClaimInstruction({
walletId: 42,
date: { year: 2025, month: 12, day: 8 },
vaultId: 2,
});Custom Instructions
These instructions wrap an ERC-7579 executeUserOp(Call[]) batch so it can be
delivered to a Flare smart account through an XRPL Payment memo. Two variants
exist:
- Hash (
UserOpCustomInstruction, opcode0xFE) - header +keccak256(packedUserOperation). Layout:[0xFE | walletId(1B) | executorFeeUBA(8B) | hash(32B)](42 bytes total). The fullPackedUserOperationis delivered to the executor off-chain; the on-chain facet verifies the hash before executing. - Memo-field (
MemoFieldUserOpCustomInstruction, opcode0xFF) - header + the full ABI-encodedPackedUserOperationcarried inside the memo. Layout:[0xFF | walletId(1B) | executorFeeUBA(8B) | packedUserOperation].
This library is dependency-free: the consumer ABI-encodes the
PackedUserOperation (and computes its keccak256 for the hash variant)
themselves - usually with viem - and passes the resulting hex string into
the instruction's input.
import { encodeAbiParameters, encodeFunctionData, keccak256 } from "viem";
import { MemoFieldUserOpCustomInstruction, UserOpCustomInstruction } from "@flarenetwork/smart-accounts-encoder";
// 1. Caller builds the ABI-encoded PackedUserOperation with their own tooling.
const callData = encodeFunctionData({
abi: iPersonalAccountAbi,
functionName: "executeUserOp",
args: [calls],
});
const packedUserOperation = encodeAbiParameters(
[
{
type: "tuple",
components: [
{ name: "sender", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "initCode", type: "bytes" },
{ name: "callData", type: "bytes" },
{ name: "accountGasLimits", type: "bytes32" },
{ name: "preVerificationGas", type: "uint256" },
{ name: "gasFees", type: "bytes32" },
{ name: "paymasterAndData", type: "bytes" },
{ name: "signature", type: "bytes" },
],
},
],
[
{
sender,
nonce,
initCode: "0x",
callData,
accountGasLimits: ("0x" + "00".repeat(32)) as `0x${string}`,
preVerificationGas: 0n,
gasFees: ("0x" + "00".repeat(32)) as `0x${string}`,
paymasterAndData: "0x",
signature: "0x",
},
]
);
// 2. Memo-field variant - the entire UserOperation rides inside the XRPL memo.
const memoFieldMemo = new MemoFieldUserOpCustomInstruction({
walletId: 0,
executorFeeUBA: 0n,
packedUserOperation,
}).encode();
// 3. Hash variant - caller computes the digest, full data goes to the executor.
const hashMemo = new UserOpCustomInstruction({
walletId: 0,
executorFeeUBA: 0n,
userOperationHash: keccak256(packedUserOperation),
}).encode();Decoding without knowing the instruction type
When you receive a raw memo and do not know which instruction it is, use
decodeInstruction. It reads the leading instruction-id byte, dispatches to the
matching class, and returns a validated instance (length and id byte are
checked). Narrow the result with instanceof:
import { decodeInstruction, FXRPRedeemInstruction } from "@flarenetwork/smart-accounts-encoder";
const instruction = decodeInstruction(memoHex);
if (instruction instanceof FXRPRedeemInstruction) {
console.log(instruction.data.value); // bigint
}Amounts are bigint
All token amounts (Drop, Lot) are bigint, not number. On-chain amounts
are encoded into 8- and 10-byte fields that exceed the JS safe-integer range, so
a number would silently lose precision. Pass amounts as bigint literals
(value: 1000000n).
Errors
Encoding and decoding fail loud with typed errors rather than returning wrong
data. Catch SmartAccountsEncoderError for any failure, or narrow to
EncodingError (malformed/out-of-range input on encode) or DecodingError
(wrong length, unknown or mismatched instruction id, non-canonical padding).
import { DecodingError } from "@flarenetwork/smart-accounts-encoder";
try {
FXRPRedeemInstruction.decode(memoHex);
} catch (error) {
if (error instanceof DecodingError) {
// malformed or wrong-type memo
}
}Hex utilities
The validated hex helpers toHex and fromHex are also exported. fromHex
requires a 0x prefix and an even number of hex digits, and throws an
EncodingError on malformed input:
import { toHex, fromHex } from "@flarenetwork/smart-accounts-encoder";
const hex = toHex(Uint8Array.from([42, 0, 0, 0])); // "0x2a000000"
const bytes = fromHex(hex); // Uint8Array