| name | fastset |
| version | 1.3.0 |
| description | Interact with the FastSet network — a high-performance settlement layer. Query accounts, submit transactions, transfer and mint tokens via the JSON-RPC proxy API. Supports Ed25519 wallet operations. |
| author | Pi-Squared-Inc |
| homepage | https://github.com/Pi-Squared-Inc/fastset-rpc-docs |
FastSet Network Skill
Interact with the FastSet network via the JSON-RPC proxy API.
🚨 Base URL: https://proxy.fastset.xyz
⚡ New here? Start at Getting Started — you'll have a funded wallet in 2 minutes.
📋 Already set up? Jump to Quick Reference.
🔧 Need to sign transactions? See BCS Type Definitions and Complete Working Example.
Quick Reference
| Action | Method | Endpoint |
|---|
| Query account | proxy_getAccountInfo | Check balance, nonce, state |
| Submit transaction | proxy_submitTransaction | Transfer tokens, create tokens, etc. |
| Faucet (testnet) | proxy_faucetDrip | Get test tokens (returns null on success) |
| Token info | proxy_getTokenInfo | Query custom token metadata |
Getting Started
Step 1: Install Dependencies
npm init -y
npm pkg set type=module
npm install @mysten/bcs @noble/ed25519 @noble/hashes
⚠️ ESM required: Your package.json must have "type": "module" (or use .mjs file extension).
⚠️ Version note: This skill targets @noble/ed25519 v3.x. If using v2.x, see Version Compatibility.
Step 2: Required Setup
Every script needs these before using ed25519:
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha2.js";
ed.hashes.sha512 = (...m) => sha512(ed.etc.concatBytes(...m));
BigInt.prototype.toJSON = function () { return Number(this); };
⚠️ Import path: Use @noble/hashes/sha2.js (with .js extension) — ESM requires it.
Step 3: Generate a Wallet
const privateKey = ed.utils.randomSecretKey();
const publicKey = ed.getPublicKey(privateKey);
console.log("Address:", Buffer.from(publicKey).toString("hex"));
Step 4: Fund via Faucet
await rpc("proxy_faucetDrip", {
recipient: publicKey,
amount: "de0b6b3a7640000",
token_id: null
});
Step 5: Check Your Balance
const info = await rpc("proxy_getAccountInfo", {
address: publicKey,
token_balances_filter: null,
state_key_filter: null,
certificate_by_nonce: null,
});
console.log("Balance:", info.result.balance);
console.log("Nonce:", info.result.next_nonce);
Response fields:
| Field | Type | Description |
|---|
balance | hex string | Native token balance (e.g., "de0b6b3a7640000") |
next_nonce | integer | Next valid nonce for transactions |
token_balance | array | Custom token balances (native balance is in balance) |
sender | array | Account address echo |
pending_confirmation | number/null | Pending transaction count (null for new accounts) |
requested_state | array | Account state if requested (empty array [] by default) |
requested_certificates | array | Certificates if requested by nonce |
requested_validated_transaction | object/null | Validated transaction if requested |
The rpc() helper is defined in Complete Working Example below.
Core Concepts
- Addresses: 32-byte Ed25519 public keys — sent as JSON arrays of 32 unsigned integers (byte arrays), not hex strings
- Nonce: Auto-incrementing
u64 per account (start at 0). Always fetch from proxy_getAccountInfo → next_nonce before submitting
- Amounts: Hex-encoded 256-bit integers in JSON (e.g.,
"ffff" = 65535). BCS encodes these as 32-byte little-endian u256 automatically
- Timestamps:
timestamp_nanos is a u128 (use BigInt in TypeScript: BigInt(Date.now()) * 1_000_000n)
- Native Token ID:
[0xfa, 0x57, 0x5e, 0x70, 0, 0, ..., 0] (32 bytes)
- Signatures: Ed25519 over
"Transaction::" + BCS(transaction)
- Transaction fees: Transfers incur a fee (~0.01 SET). The sender's balance decreases by the transfer amount plus the fee
- JSON serialization:
Uint8Array must be converted via Array.from() — see helper below
BCS Type Definitions
Required for transaction signing. The BCS schema must match on-chain types exactly.
import { bcs } from "@mysten/bcs";
const AmountBcs = bcs.u256().transform({
input: (val) => BigInt(`0x${val}`).toString(),
});
const TokenTransfer = bcs.struct("TokenTransfer", {
token_id: bcs.bytes(32),
amount: AmountBcs,
user_data: bcs.option(bcs.bytes(32)),
});
const TokenCreation = bcs.struct("TokenCreation", {
token_name: bcs.string(),
decimals: bcs.u8(),
initial_amount: AmountBcs,
mints: bcs.vector(bcs.bytes(32)),
user_data: bcs.option(bcs.bytes(32)),
});
const AddressChange = bcs.enum("AddressChange", {
Add: bcs.tuple([]),
Remove: bcs.tuple([]),
});
const TokenManagement = bcs.struct("TokenManagement", {
token_id: bcs.bytes(32),
update_id: bcs.u64(),
new_admin: bcs.option(bcs.bytes(32)),
mints: bcs.vector(bcs.tuple([AddressChange, bcs.bytes(32)])),
user_data: bcs.option(bcs.bytes(32)),
});
const Mint = bcs.struct("Mint", {
token_id: bcs.bytes(32),
amount: AmountBcs,
});
const ClaimType = bcs.enum("ClaimType", {
TokenTransfer: TokenTransfer,
TokenCreation: TokenCreation,
TokenManagement: TokenManagement,
Mint: Mint,
StateInitialization: bcs.struct("StateInitialization", { dummy: bcs.u8() }),
StateUpdate: bcs.struct("StateUpdate", { dummy: bcs.u8() }),
ExternalClaim: bcs.struct("ExternalClaim", { data: bcs.bytes(32) }),
StateReset: bcs.struct("StateReset", { dummy: bcs.u8() }),
JoinCommittee: bcs.struct("JoinCommittee", { dummy: bcs.u8() }),
LeaveCommittee: bcs.struct("LeaveCommittee", { dummy: bcs.u8() }),
ChangeCommittee: bcs.struct("ChangeCommittee", { dummy: bcs.u8() }),
Batch: bcs.vector(bcs.enum("Operation", {
TokenTransfer: bcs.struct("TokenTransferOperation", {
token_id: bcs.bytes(32),
recipient: bcs.bytes(32),
amount: AmountBcs,
user_data: bcs.option(bcs.bytes(32))
}),
TokenCreation: TokenCreation,
TokenManagement: TokenManagement,
Mint: bcs.struct("MintOperation", {
token_id: bcs.bytes(32),
recipient: bcs.bytes(32),
amount: AmountBcs
})
}))
});
const TransactionBcs = bcs.struct("Transaction", {
sender: bcs.bytes(32),
recipient: bcs.bytes(32),
nonce: bcs.u64(),
timestamp_nanos: bcs.u128(),
claim: ClaimType,
archival: bcs.bool(),
});
Important: The ClaimType enum variant ordering determines the BCS discriminant byte.
If you add more variants, they must match on-chain ordering. See typescript-examples/fastset-types.ts for the complete set.
Transferring Tokens
Build the Transaction
const transaction = {
sender: senderPubKey,
recipient: recipientPubKey,
nonce: nextNonce,
timestamp_nanos: BigInt(Date.now()) * 1_000_000n,
claim: {
TokenTransfer: {
token_id: SET_TOKEN_ID,
amount: "ffff",
user_data: null,
}
},
archival: false,
};
Sign It
const msghead = new TextEncoder().encode("Transaction::");
const msgbody = TransactionBcs.serialize(transaction).toBytes();
const msg = new Uint8Array(msghead.length + msgbody.length);
msg.set(msghead, 0);
msg.set(msgbody, msghead.length);
const signature = ed.sign(msg, privateKey);
Submit It
const result = await rpc("proxy_submitTransaction", {
transaction,
signature: { Signature: signature },
});
Note: Transactions incur a fee (~0.01 SET). The sender's balance will decrease by the transfer amount plus the fee.
Claim Types
| Type | BCS Index | Purpose |
|---|
TokenTransfer | 0 | Transfer tokens between accounts |
TokenCreation | 1 | Create new custom token |
TokenManagement | 2 | Modify token (admin, minters) |
Mint | 3 | Mint additional supply (authorized minters) |
ExternalClaim | 4 | Submit arbitrary data with verifier signatures |
Batch | 5 | Bundle multiple operations |
Complete Working Example
Self-contained Node.js script — generates two wallets, funds one, transfers tokens to the other.
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha2.js";
import { bcs } from "@mysten/bcs";
ed.hashes.sha512 = (...m) => sha512(ed.etc.concatBytes(...m));
BigInt.prototype.toJSON = function () { return Number(this); };
const PROXY = "https://proxy.fastset.xyz";
const SET_TOKEN_ID = new Uint8Array(32);
SET_TOKEN_ID.set([0xfa, 0x57, 0x5e, 0x70], 0);
const AmountBcs = bcs.u256().transform({
input: (val) => BigInt(`0x${val}`).toString(),
});
const TokenTransfer = bcs.struct("TokenTransfer", {
token_id: bcs.bytes(32), amount: AmountBcs, user_data: bcs.option(bcs.bytes(32)),
});
const ClaimType = bcs.enum("ClaimType", { TokenTransfer });
const TransactionBcs = bcs.struct("Transaction", {
sender: bcs.bytes(32), recipient: bcs.bytes(32),
nonce: bcs.u64(), timestamp_nanos: bcs.u128(),
claim: ClaimType, archival: bcs.bool(),
});
const toJSON = (data) => JSON.stringify(data, (k, v) =>
v instanceof Uint8Array ? Array.from(v) : v);
async function rpc(method, params) {
const res = await fetch(PROXY, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: toJSON({ jsonrpc: "2.0", id: 1, method, params }),
});
const json = await res.json();
if (json.error) throw new Error(`RPC error: ${JSON.stringify(json.error)}`);
return json;
}
const sk1 = ed.utils.randomSecretKey();
const pk1 = ed.getPublicKey(sk1);
const sk2 = ed.utils.randomSecretKey();
const pk2 = ed.getPublicKey(sk2);
console.log("Wallet 1:", Buffer.from(pk1).toString("hex"));
console.log("Wallet 2:", Buffer.from(pk2).toString("hex"));
await rpc("proxy_faucetDrip", { recipient: pk1, amount: "de0b6b3a7640000", token_id: null });
console.log("Faucet: funded wallet 1");
const info = await rpc("proxy_getAccountInfo", {
address: pk1, token_balances_filter: null, state_key_filter: null, certificate_by_nonce: null,
});
console.log("Balance:", info.result.balance, "| Nonce:", info.result.next_nonce);
const tx = {
sender: pk1, recipient: pk2, nonce: info.result.next_nonce,
timestamp_nanos: BigInt(Date.now()) * 1_000_000n,
claim: { TokenTransfer: { token_id: SET_TOKEN_ID, amount: "ffff", user_data: null } },
archival: false,
};
const head = new TextEncoder().encode("Transaction::");
const body = TransactionBcs.serialize(tx).toBytes();
const msg = new Uint8Array(head.length + body.length);
msg.set(head, 0); msg.set(body, head.length);
const sig = ed.sign(msg, sk1);
const result = await rpc("proxy_submitTransaction", {
transaction: tx, signature: { Signature: sig },
});
console.log("Transfer submitted:", result.result ? "✅ success" : "❌ failed");
const info1 = await rpc("proxy_getAccountInfo", {
address: pk1, token_balances_filter: null, state_key_filter: null, certificate_by_nonce: null,
});
const info2 = await rpc("proxy_getAccountInfo", {
address: pk2, token_balances_filter: null, state_key_filter: null, certificate_by_nonce: null,
});
console.log("Wallet 1 balance:", info1.result.balance);
console.log("Wallet 2 balance:", info2.result.balance);
Version Compatibility
@noble/ed25519 v2.x vs v3.x
| Feature | v2.x | v3.x (current) |
|---|
| Import SHA-512 | @noble/hashes/sha2 | @noble/hashes/sha2.js |
| Set SHA-512 | ed.etc.sha512Sync = ... | ed.hashes.sha512 = ... |
| Generate key | ed.utils.randomPrivateKey() | ed.utils.randomSecretKey() |
| Get public key | ed.getPublicKey(sk) | ed.getPublicKey(sk) (same) |
| Sign | ed.sign(msg, sk) | ed.sign(msg, sk) (same) |
To pin v2: npm install @noble/ed25519@2 @noble/hashes@1
curl Examples
Query Account
curl -s -X POST https://proxy.fastset.xyz \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": 1,
"method": "proxy_getAccountInfo",
"params": {
"address": [134, 108, 191, 240, 166, 239, 50, ...],
"token_balances_filter": null,
"state_key_filter": null,
"certificate_by_nonce": null
}
}'
Faucet Drip
curl -s -X POST https://proxy.fastset.xyz \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": 1,
"method": "proxy_faucetDrip",
"params": {
"recipient": [134, 108, 191, 240, 166, 239, 50, ...],
"amount": "de0b6b3a7640000",
"token_id": null
}
}'
Both address and recipient are JSON arrays of 32 unsigned integers (byte values of the Ed25519 public key).
Error Handling
| Error | Cause | Fix |
|---|
| Invalid nonce | Wrong sequence number | Fetch next_nonce from proxy_getAccountInfo |
| Insufficient balance | Not enough tokens | Use faucet or receive transfer first |
| Invalid signature | Wrong signing process | Ensure "Transaction::" + BCS(tx) prefix |
No more params | Missing required RPC fields | Check all required params are present |
sha512 / hashes.sha512 not set | Missing ed25519 setup | Add ed.hashes.sha512 = ... (see Step 2) |
| BigInt serialization error | JSON.stringify can't handle BigInt | Add BigInt.prototype.toJSON (see Step 2) |
| Address serialized as object | Uint8Array not converted for JSON | Use Array.from() or toJSON helper |
Faucet returns null | This is success, not an error | Check json.error field for real errors |
Rust
Use the bcs crate with serde for serialization. See rust-examples/ for a complete implementation.
API Reference
Full JSON-RPC specification: docs/proxy/rpc.md