A Pythonic interface for building NEAR smart contracts.
- Class-Based Contracts - Clean, intuitive inheritance pattern
- Pythonic Storage - Dictionary-like interface for on-chain data
- Elegant Promise API - Intuitive chaining for cross-contract calls
- Native Exception Handling - Use try/except like regular Python
- Ergonomic Context Access - Properties instead of static methods
uv init your-contract
cd your-contract
uv add near-sdk-pyfrom near_sdk_py import Contract, view, call, init
class GreetingContract(Contract):
@init
def initialize(self, default_message="Hello"):
self.storage["greeting"] = default_message
return {"success": True}
@view
def get_greeting(self):
return self.storage.get("greeting", "Hello, world!")
@call
def set_greeting(self, message):
self.storage["greeting"] = message
return {"success": True}uvx nearc build
near deploy your-contract.testnet greeting_contract.wasm# Call a view method (free, no gas needed)
near view your-contract.testnet get_greeting
# Call a change method (requires gas)
near call your-contract.testnet set_greeting '{"message":"Hello, NEAR!"}' --accountId your-account.testnetAll contracts inherit from the Contract class, which provides:
from near_sdk_py import Contract, call, view
class MyContract(Contract):
@init
def initialize(self):
self.storage["owner"] = self.predecessor_account_id
@view
def get_status(self):
# Access context properties directly
return {
"contract_id": self.current_account_id,
"caller": self.predecessor_account_id,
"owner": self.storage.get("owner"),
"block_height": self.block_height,
"timestamp": self.block_timestamp
}
@call
def update_config(self, key, value):
# Simple validation
self.assert_owner()
# Use dict-like storage access
self.storage[key] = value
# Easy logging
self.log_event("config_updated", {"key": key})
return {"success": True}The storage interface is now more Pythonic with dictionary-like access:
# Store values with dict syntax
self.storage["key"] = "value" # String value
self.storage["config"] = {"active": True} # Any JSON-serializable value
# Retrieve values with easy defaults
value = self.storage.get("key", "default") # With default
config = self.storage["config"] # Direct access
# Check for keys
if "owner" in self.storage:
# Do something
# Delete keys
del self.storage["old_key"]Access blockchain context directly as properties:
# Account information
account_id = self.current_account_id # This contract's account
caller = self.predecessor_account_id # Who called this function
signer = self.signer_account_id # Who signed the transaction
# Transaction details
deposit = self.attached_deposit # NEAR tokens attached
gas = self.prepaid_gas # Gas allocated to this call
# Blockchain state
height = self.block_height # Current block height
timestamp = self.block_timestamp # Current block timestamp# Simple logging
self.log_info("Operation completed successfully")
self.log_warning("Approaching storage limit")
self.log_error("Transaction failed: insufficient deposit")
# NEP-standard event logging
self.log_event("nft_mint", {
"token_id": "token-123",
"owner_id": "alice.near",
"memo": "Happy birthday!"
})Use native Python exceptions for error handling:
from near_sdk_py import Contract, call, InvalidInput, AccessDenied
class MyContract(Contract):
@call
def restricted_method(self):
if self.predecessor_account_id != self.storage.get("owner"):
raise AccessDenied("Only the owner can call this method")
if self.attached_deposit < self.storage.get("min_deposit", 0):
raise InvalidInput("Insufficient deposit")
try:
# Do something that might fail
result = some_operation()
except Exception as e:
raise ContractError(f"Operation failed: {e}")The SDK provides a powerful Promises API for making cross-contract calls with elegant method chaining:
from near_sdk_py import call, callback, CrossContract, PromiseResult
class CrossContractExample(Contract):
@call
def get_token_balance(self, token_contract_id: str, account_id: str):
# Create a contract instance and chain the operations
promise = (
CrossContract(token_contract_id)
.call("ft_balance_of", account_id=account_id)
.then("process_balance", account_id=account_id)
.value()
)
return promise
@callback
def process_balance(self, result: PromiseResult, account_id: str):
if result.success:
return {
"account_id": account_id,
"balance": result.data
}
else:
return {
"account_id": account_id,
"error": "Failed to retrieve balance"
}Key features of the Promises API:
- Fluent interface for chaining operations
- Automatic callback registration
- Promise result handling
- Support for batch operations
Read the Promises documentation β
The Contract class provides built-in validation helpers:
from near_sdk_py import call, ONE_NEAR
class SecureContract(Contract):
@call
def transfer_ownership(self, new_owner):
self.assert_one_yocto() # Ensures tx is signed (non-delegated)
self.assert_owner() # Verifies caller is the owner
# Implementation...
@call
def place_bid(self, token_id):
self.assert_min_deposit(ONE_NEAR) # At least 1 NEAR required
# Implementation...The NEAR Python SDK works seamlessly with near-pytest, a pytest-native framework for testing NEAR smart contracts. Together, they provide a powerful toolchain for developing and testing contracts.
- Zero-config setup - Automatic sandbox management, contract compilation, and account creation
- Lightning-fast tests - State snapshots between tests make test suites run significantly faster
- Intuitive API - Simple, Pythonic interfaces for interacting with contracts and accounts
from near_pytest.testing import NearTestCase
class TestGreetingContract(NearTestCase):
@classmethod
def setup_class(cls):
super().setup_class()
# Compile the contract
wasm_path = cls.compile_contract(
"contracts/greeting.py",
single_file=True
)
# Deploy the contract
cls.contract_account = cls.create_account("contract")
cls.instance = cls.deploy_contract(cls.contract_account, wasm_path)
# Create test user
cls.user = cls.create_account("user")
# Save state for future resets - this is crucial for performance
cls.save_state()
def setup_method(self):
# Reset to initial state before each test method
# This makes tests run much faster than re-deploying
self.reset_state()
def test_greeting(self):
# Set greeting as user
result = self.instance.call_as(
account=self.user,
method_name="set_greeting",
args={"message": "Hello, tests!"}
)
result = json.loads(result)
assert result["success"] == True
# Get greeting
greeting = self.instance.call_as(
account=self.user,
method_name="get_greeting"
)
assert greeting == "Hello, tests!"Simply run with pytest:
pytest test_greeting_contract.py -vThe state management pattern (save_state/reset_state) is what makes near-pytest tests run significantly faster than traditional approaches. Rather than re-deploying contracts and recreating accounts for each test, it:
- Sets up the full environment once in
setup_class - Takes a snapshot of the blockchain state
- Quickly resets to this snapshot before each test
This approach can make test suites run orders of magnitude faster, especially as your test suite grows.
The SDK includes starter templates for common contract types:
- Fungible Tokens - Create your own token
- Non-Fungible Tokens - Build an NFT collection
- DAO Governance - Decentralized organizations
- DeFi Contracts - Financial applications
- Group related data in a single storage entry to reduce calls
- Implement pagination for methods that return large collections
- For collections, use the SDK's storage primitives which are optimized for gas efficiency
We welcome contributions! Please see CONTRIBUTING.md for details on how to submit pull requests, report issues, and suggest improvements.
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by r-near