near-kit
A Pythonic SDK for NEAR Protocol. Feels like requests.
import near
near.view("counter.near", "get_count") # one line, no setup
near.balance("alice.near") # -> Amount('42.5 NEAR')
Human-readable everywhere — "10 NEAR" and "30 Tgas", never
10000000000000000000000000. Typed errors, exact integer math, sync and
async, post-quantum keys, and the full nearcore 2.13 action set (global
contracts, gas keys, deterministic accounts). The Python sibling of
near-kit for TypeScript.
Install
uv add near-kit # or: pip install near-kit
Python 3.11+.
The rule of the API
Reads are stateless; if it signs, you need a client.
Module functions cover one-off reads, requests.get()-style. A Near
client holds the stateful things: your signer, a nonce cache, and a
connection pool.
from near import Near
# Explicit config...
client = Near(network="testnet",
account_id="alice.testnet",
private_key="ed25519:...")
# ...or zero-argument discovery: NEAR_NETWORK / NEAR_ACCOUNT_ID /
# NEAR_PRIVATE_KEY env vars, falling back to ~/.near-credentials.
client = Near()
# ...or straight from NEAR CLI credentials:
client = Near.from_file("alice.testnet", network="testnet")
Reads
client.view("counter.testnet", "get_count")
client.balance() # your balance -> Amount
client.balance("bob.testnet") # anyone's
client.account("bob.testnet") # AccountView (balance, storage, code hash)
client.account_exists("bob.testnet")
client.access_keys("bob.testnet")
client.transaction_status(tx_hash)
client.rpc("block", {"finality": "final"}) # escape hatch: any RPC method
Writes
client.send("bob.testnet", "5 NEAR")
client.call("counter.testnet", "increment", {"by": 2},
deposit="0.1 NEAR", gas="30 Tgas")
Multi-action transactions are just data — a list of actions, one receiver, executed atomically:
from near import create_account, transfer, deploy_contract, function_call, add_full_access_key
client.send_transaction(
"sub.alice.testnet",
actions=[
create_account(),
transfer("5 NEAR"),
add_full_access_key(key.public_key),
deploy_contract(wasm_bytes),
function_call("init", {"owner": "alice.testnet"}),
],
)
Tokens
NEP-141 fungible tokens are first-class: amounts are human strings in the
token's own decimals, and register=True batches the receiver's
storage_deposit into the same atomic transaction when they aren't
registered yet.
client.ft_transfer("usdt.tether-token.near", "bob.near", "5.25 USDT", register=True)
balance = client.ft_balance("usdt.tether-token.near") # TokenAmount, exact int
print(balance) # 5.25 USDT
balance.display # Decimal('5.25')
NFTs (NEP-171) get the same treatment: nft_transfer attaches the required
1 yoctoNEAR, nft_token / nft_tokens_for_owner cover lookups.
Async
AsyncNear is the same surface, awaited — for FastAPI services, relayers,
and MCP servers:
from near import AsyncNear
async with AsyncNear() as client:
await client.call("counter.testnet", "increment")
results = await asyncio.gather(*(client.send(acc, "0.1 NEAR") for acc in accounts))
Concurrent transactions from one account just work — the client manages nonces and retries collisions automatically.
Amounts are exact
Amount is an int subclass denominated in yoctoNEAR, so arithmetic and
comparisons are exact; it parses and prints human strings. Bare numbers are
rejected at API boundaries — unit confusion is a bug that should not compile.
from near import Amount
bal = client.balance() # Amount(5250000000000000000000000)
str(bal) # '5.25 NEAR'
bal.as_near # Decimal('5.25')
bal > Amount("1 NEAR") # True
client.send("bob.near", 5) # UnitParseError: write "5 NEAR" or "5 yocto"
Typed errors
from near import ContractPanicError, AccountNotFoundError
try:
client.call("app.near", "buy", deposit="1 NEAR")
except ContractPanicError as e:
print(e.panic) # the contract's panic message
print(e.logs) # contract logs leading up to it
Every error derives from NearError with a stable .code and a
.retryable flag.
Testing your app: the sandbox is a database
Run NEAR like you'd run postgres in CI:
docker run -d -p 3030:3030 nearprotocol/sandbox:2.13.4
The sandbox root account's key is deterministic (derived from the image's test seed), so nothing needs to be copied out of the container:
from near import Near
from near.testing import sandbox_signer
near = Near(rpc_url="http://localhost:3030", signer=sandbox_signer())
near.send("anything.sandbox", ...) # root account, fully funded
GitHub Actions:
services:
near-sandbox:
image: nearprotocol/sandbox:2.13.4
ports: ["3030:3030"]
Or let pytest do all of it — installing near-kit registers a plugin. Its
near_sandbox fixture reuses NEAR_SANDBOX_URL or localhost:3030 when
reachable, otherwise starts (and later removes) a Docker sandbox on a free
port; sandbox_near is a Near client signing as the root account:
from near import add_full_access_key, create_account, generate_key, transfer
from near.testing import fast_forward
def test_my_app(sandbox_near):
key = generate_key()
sandbox_near.send_transaction( # root creates + funds a fresh account
"alice.sandbox",
actions=[create_account(), transfer("10 NEAR"), add_full_access_key(key.public_key)],
)
fast_forward(sandbox_near, 100) # time travel: 100 blocks, no waiting
Meta-transactions (NEP-366)
User signs, relayer pays gas:
# User side — no gas spent:
signed = client.sign_delegate("app.near", actions=[function_call("claim", {})])
payload = encode_signed_delegate(signed) # base64, POST it to your relayer
# Relayer side:
relayer.send_delegate(payload)
nearcore 2.13's DelegateV2 (NEP-611, the gas-key flavour of delegate
actions) is deliberately not modeled while the feature is reworked upstream.
Global contracts
Publish WASM once; any account then deploys it by reference instead of paying to store its own copy:
from near import publish_contract, use_global_contract
# Publisher: identified by your account (re-publish to update every user)
# or by code hash (immutable).
client.send_transaction(me, actions=[publish_contract(wasm_bytes)])
client.send_transaction(me, actions=[publish_contract(wasm_bytes, identified_by="hash")])
# Anyone: run the published code on their own account.
client.send_transaction("app.alice.near",
actions=[use_global_contract(account_id="publisher.near")])
client.global_contract(account_id="publisher.near") # ContractCode(code=..., hash=...)
client.global_contract_exists(code_hash="5FzD...")
client.contract_code("app.alice.near") # the code an account runs
Gas keys
A gas key is an access key with its own prepaid balance for gas and several independent nonce lanes, so one key can keep many transactions in flight (nearcore 2.13):
from near import add_gas_key, transfer_to_gas_key, withdraw_from_gas_key
client.send_transaction(me, actions=[add_gas_key(gas_pk, num_nonces=8),
transfer_to_gas_key(gas_pk, "5 NEAR")])
# Sign with the gas key and pick a lane; gas comes out of the key's balance,
# deposits still come out of the account.
as_gas_key = client.with_signer(KeyPairSigner(me, gas_key))
as_gas_key.send_transaction("app.near", [function_call("ping")], gas_key_index=3)
client.gas_key_nonces(me, gas_pk) # one nonce per lane
client.access_key(me, gas_pk).gas_key_balance # what is left for gas
client.send_transaction(me, actions=[withdraw_from_gas_key(gas_pk, "1 NEAR")])
Any key can also ask for strict_nonce=True, which makes the node accept
only "current nonce + 1" — for pipelines that must stay strictly sequential.
Concurrent strict sends on one key (or lane) take turns inside the client,
so all of them land; ordinary sends keep overlapping freely.
Deterministic accounts (NEP-616)
An account whose ID is a function of its initial state — a global contract plus seed storage — so the address is known before it exists and anyone can bring it to life:
from near import deterministic_state_init, derive_deterministic_account_id
action = deterministic_state_init(account_id="publisher.near",
data={b"owner": b"alice.near"}, deposit="1 NEAR")
account_id = derive_deterministic_account_id(action.state_init) # "0s" + 40 hex chars
client.send_transaction(account_id, actions=[action])
The seed map is encoded canonically (keys sorted bytewise, as nearcore's
BTreeMap) and the node re-derives the ID, so a mismatch is rejected instead
of creating a stray account. nearcore master's UniversalStateInit
(unreleased) is not modeled yet.
Message signing (NEP-413)
Off-chain auth ("login with NEAR"):
signed = client.sign_message("Login to MyApp", recipient="myapp.com")
# Server side:
from near import verify_message
verify_message(signed, "Login to MyApp", "myapp.com", nonce)
Post-quantum keys
ML-DSA-65 (FIPS 204) keys work everywhere ed25519 keys do:
from near import MlDsa65KeyPair
pq = MlDsa65KeyPair.generate()
client.send_transaction(account_id, actions=[add_full_access_key(pq.public_key)])
Custom signers (KMS, HSM)
Anything with account_id, public_key, and sign(message) -> bytes is a
signer:
class KmsSigner:
account_id = "treasury.near"
public_key = PublicKey.parse("ed25519:...")
def sign(self, message: bytes) -> bytes:
return kms.sign(key_id=..., message=message)
client = Near(network="mainnet", signer=KmsSigner())
What's under the hood
Borsh serialization via pyborsh (Pydantic-native, byte-verified against Rust), ed25519/ML-DSA via pyca/cryptography, NEP-616 Keccak-256 via pycryptodome, HTTP via httpx. Every transaction byte this library produces is verified end-to-end against a real nearcore node in CI.
License
MIT
Metadata
Release files for near-kit 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| near_kit-1.3.0.tar.gz | 332.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| near_kit-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 389.9 kB
Release files / near_kit-1.3.0.tar.gz
| Download URL | near_kit-1.3.0.tar.gz |
|---|---|
| Size | 332.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6f2756bbf39c5c04a5cb0b744b3c6574f41350463a5fc90eaa00f8ad4ab679db
|
|
BLAKE2b-256 checksum How to use checksums |
8bb87ab902cb2424140471f53bfc6102357a8507345853d381f868477a4aedd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / near_kit-1.3.0-py3-none-any.whl
| Download URL | near_kit-1.3.0-py3-none-any.whl |
|---|---|
| Size | 57.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b44d0fa46abef5ea9e48d6e1c18e2689336e9fde88f407dbef08332fc34ae8e3
|
|
BLAKE2b-256 checksum How to use checksums |
52b52d48699b1214e13ffd5abe3d8baba66b1a5474ac590126e36bea432a1089
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log