EIP-2612는 ERC-20 Permit 확장으로, 토큰 소유자가 온체인 승인 트랜잭션 대신 오프체인 서명을 통해 스마트 컨트랙트나 다른 계정에 지출 한도를 부여할 수 있도록 합니다. 서명된 데이터를 단일 permit() 호출에 포함시킴으로써 사용자는 가스비를 절약하고, 통합자는 승인과 실행을 하나의 원자적 트랜잭션으로 묶을 수 있습니다.
Since Solana lets you combine approval and action in one atomic transaction and allows someone else to cover the fees by default, the two pain points that EIP-2612 solves on Ethereum (duplicated approvals and user-paid gas) are already removed at the protocol level. There is no need for an additional permit function, typed-data domain, or contract upgrade; the standard SPL-Token program satisfies those requirements out of the box.
On Ethereum, transferring ERC20 tokens between two addresses often requires approve + transferFrom if you're doing it on behalf of a user. On Solana, each user already has an ATA (Associated Token Accounts) recognized by the Token Program. A single Solana transaction can atomically call multiple instructions, so no separate approval step is required. You can directly transfer from one user's token account to another in one go.
Every Solana transaction explicitly names both its signers and a fee-payer. A user can sign the transaction while designating a third party (a wallet service, dApp backend, or relayer) to pay the fees. The protocol therefore delivers the same gasless user experience that EIP-2612 targets, but without introducing a special off-chain signature scheme such as EIP-712.
Below are two minimal, copy-paste-ready examples that map the two most common permit -> action scenarios to Solana's native primitives. Both use @solana/web3.js and @solana/spl-token.
The owner both signs and pays the fee. All we do is bundle Approve -> Transfer into a single atomic transaction, no extra permit function required.
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import {
createApproveInstruction,
createTransferInstruction,
getAssociatedTokenAddressSync
} from "@solana/spl-token";
const connection = new Connection("https://api.devnet.solana.com");
const owner = Keypair.generate();
const delegate = owner.publicKey;
const recipient = new PublicKey("DESTINATION_WALLET");
const mint = new PublicKey("TOKEN_MINT");
const amount = 1_000_000;
const ownerATA = getAssociatedTokenAddressSync(mint, owner.publicKey);
const recipientATA = getAssociatedTokenAddressSync(mint, recipient);
const ixApprove = createApproveInstruction(ownerATA, delegate, owner.publicKey, amount);
const ixTransfer = createTransferInstruction(ownerATA, recipientATA, owner.publicKey, amount);
const tx = new Transaction().add(ixApprove, ixTransfer);
tx.feePayer = owner.publicKey;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.sign(owner);
const sig = await connection.sendRawTransaction(tx.serialize());
console.log("Sent (self-sponsored):", sig);Here the holder signs the instruction, but a relayer (or dApp back-end) covers the SOL fee. Solana supports this natively via the feePayer field plus partial signatures.
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { createTransferInstruction, getAssociatedTokenAddressSync } from "@solana/spl-token";
const connection = new Connection("https://api.devnet.solana.com");
const owner = Keypair.generate(); // replace with real keypair
const feePayer = Keypair.generate(); // replace with real keypair
const recipient = new PublicKey("DESTINATION_WALLET");
const mint = new PublicKey("TOKEN_MINT");
const amount = 500_000;
const ownerATA = getAssociatedTokenAddressSync(mint, owner.publicKey);
const recipientATA = getAssociatedTokenAddressSync(mint, recipient);
const ix = createTransferInstruction(ownerATA, recipientATA, owner.publicKey, amount);
const tx = new Transaction().add(ix);
tx.feePayer = feePayer.publicKey;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.partialSign(owner);
tx.sign(feePayer);
const sig = await connection.sendRawTransaction(tx.serialize());
console.log("Sent (relayer-sponsored):", sig);