Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
NEAR uses the Ed25519 elliptic curve — the same as Solana. NEAR implicit accounts are simply the hex-encoded 32-byte public key. Transactions use Borsh (Binary Object Representation Serializer for Hashing) serialization and are submitted via JSON-RPC.
| Property | Value |
|---|
| Curve | Ed25519 |
| Root Wallet | Solana |
| Address Format | 64-char hex string (implicit account) |
| Hashing | SHA-256 |
| Serialization | Borsh |
| Smallest Unit | yoctoNEAR (1 NEAR = 10^24 yoctoNEAR) |
Dependencies
npm install @noble/curves @noble/hashes bs58
Derive Address
NEAR implicit accounts are the hex-encoded Ed25519 public key — the simplest derivation of all chains:
import bs58 from "bs58";
function deriveNearAddress(solanaAddress: string): string {
const pubkey = bs58.decode(solanaAddress);
return bytesToHex(pubkey); // 64-char hex string
}
Sign a Message
Sign the raw UTF-8 bytes of the message using the Solana wallet:
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";
async function signNearMessage(
message: string,
solWallet: WalletAccount,
): Promise<string> {
const messageBytes = new TextEncoder().encode(message);
const result = await signMessage({
walletAccount: solWallet,
message: bytesToHex(messageBytes),
});
return result.signature;
}
Verify a Signature
Verify the Ed25519 signature against the raw message bytes:
import { ed25519 } from "@noble/curves/ed25519";
function verifyNearSignature(
message: string,
signature: string,
solanaAddress: string,
): boolean {
const pubkey = bs58.decode(solanaAddress);
const messageBytes = new TextEncoder().encode(message);
const sigBytes = decodeSig(signature);
return ed25519.verify(sigBytes, messageBytes, pubkey);
}
Check Balance
Query the NEAR balance via JSON-RPC:
async function getNearBalance(accountId: string): Promise<string> {
const res = await fetch("https://rpc.testnet.near.org", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "1",
method: "query",
params: {
request_type: "view_account",
finality: "final",
account_id: accountId,
},
}),
});
const data = await res.json();
if (data.error) return "0";
const yocto = BigInt(data.result.amount);
const divisor = BigInt(10) ** BigInt(24);
const whole = yocto / divisor;
const frac = yocto % divisor;
const fracStr = frac.toString().padStart(24, "0").replace(/0+$/, "");
if (fracStr.length === 0) return whole.toString();
return `${whole}.${fracStr}`;
}
Send a Transfer
Build a Borsh-serialized transaction, sign with the Solana wallet, and submit via JSON-RPC.
Borsh Serialization Primitives
function borshU8(value: number): Uint8Array {
return new Uint8Array([value & 0xff]);
}
function borshU32(value: number): Uint8Array {
const buf = new Uint8Array(4);
buf[0] = value & 0xff;
buf[1] = (value >>> 8) & 0xff;
buf[2] = (value >>> 16) & 0xff;
buf[3] = (value >>> 24) & 0xff;
return buf;
}
function borshU64(value: bigint): Uint8Array {
const buf = new Uint8Array(8);
for (let i = 0; i < 8; i++) {
buf[i] = Number((value >> BigInt(i * 8)) & BigInt(0xff));
}
return buf;
}
function borshU128(value: bigint): Uint8Array {
const buf = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
buf[i] = Number((value >> BigInt(i * 8)) & BigInt(0xff));
}
return buf;
}
function borshString(s: string): Uint8Array {
const encoded = new TextEncoder().encode(s);
const len = borshU32(encoded.length);
return concatBytes(len, encoded);
}
Full Transfer Implementation
import { sha256 } from "@noble/hashes/sha2";
import { signMessage } from "@dynamic-labs-sdk/client";
import bs58 from "bs58";
async function sendNearTransfer(
recipient: string,
amountNear: number,
nearAddress: string,
solWallet: WalletAccount,
): Promise<string> {
const pubkey = bs58.decode(solWallet.address);
// Convert NEAR to yoctoNEAR (1 NEAR = 10^24 yoctoNEAR)
const [wholePart, fracPart = ""] = amountNear.toString().split(".");
const paddedFrac = fracPart.padEnd(24, "0").slice(0, 24);
const amountYocto = BigInt(wholePart + paddedFrac);
// Fetch access key and recent block hash
const [accessKeyResult, blockResult] = await Promise.all([
nearRpc("query", {
request_type: "view_access_key",
finality: "final",
account_id: nearAddress,
public_key: `ed25519:${bs58.encode(pubkey)}`,
}),
nearRpc("block", { finality: "final" }),
]);
const nonce = BigInt(accessKeyResult.nonce) + BigInt(1);
const blockHash = bs58.decode(blockResult.header.hash);
// Build transfer action: variant 3 (Transfer) + u128 amount
const transferAction = concatBytes(
borshU8(3), // Transfer action variant
borshU128(amountYocto),
);
// Serialize the transaction
const txBytes = concatBytes(
borshString(nearAddress), // signer_id
borshU8(0), // PublicKey variant: ed25519
pubkey, // 32-byte pubkey
borshU64(nonce),
borshString(recipient), // receiver_id
blockHash, // 32-byte block hash
borshU32(1), // number of actions
transferAction,
);
// Hash and sign
const txHash = sha256(txBytes);
const signResult = await signMessage({
walletAccount: solWallet,
message: bytesToHex(txHash),
});
const sigBytes = decodeSig(signResult.signature);
// Build signed transaction
const signedTxBytes = concatBytes(
txBytes,
borshU8(0), // Signature variant: ed25519
sigBytes, // 64-byte signature
);
// Base64 encode and submit
const signedTxBase64 = btoa(String.fromCharCode(...signedTxBytes));
const result = await nearRpc("broadcast_tx_commit", [signedTxBase64]);
return result.transaction.hash;
}