@neardefi/shade-agent-js
Version:
A library for creating Shade Agent agents in JavaScript and TypeScript
245 lines (238 loc) • 10.6 kB
text/typescript
import { Provider } from '@near-js/providers';
import { SerializedReturnValue, BlockReference, TxExecutionStatus } from '@near-js/types';
interface TcbInfo {
mrtd: string;
rtmr0: string;
rtmr1: string;
rtmr2: string;
rtmr3: string;
os_image_hash: string;
compose_hash: string;
device_id: string;
app_compose: string;
event_log: EventLog[];
}
interface EventLog {
imr: number;
event_type: number;
digest: string;
event: string;
event_payload: string;
}
interface DstackAttestationForContract {
quote: number[];
collateral: {
pck_crl_issuer_chain: string;
root_ca_crl: string;
pck_crl: string;
tcb_info_issuer_chain: string;
tcb_info: string;
tcb_info_signature: string;
qe_identity_issuer_chain: string;
qe_identity: string;
qe_identity_signature: string;
};
tcb_info: TcbInfo;
}
interface Measurements {
/** MRTD (Measurement of Root of Trust for Data) - identifies the virtual firmware. */
mrtd: string;
/** RTMR0 (Runtime Measurement Register 0) - typically measures the bootloader, virtual firmware data, and configuration. */
rtmr0: string;
/** RTMR1 (Runtime Measurement Register 1) - typically measures the OS kernel, boot parameters, and initrd (initial ramdisk). */
rtmr1: string;
/** RTMR2 (Runtime Measurement Register 2) - typically measures the OS application. */
rtmr2: string;
}
interface FullMeasurements {
/** Expected RTMRs (Runtime Measurement Registers). */
rtmrs: Measurements;
/** Expected digest for the key-provider event. */
key_provider_event_digest: string;
/** Expected app_compose hash payload. */
app_compose_hash_payload: string;
}
/**
* Configuration object for creating a ShadeClient instance
*/
interface ShadeConfig {
/** Network ID ('testnet' or 'mainnet'), defaults to 'testnet' if not provided */
networkId?: "testnet" | "mainnet";
/** The NEAR contract ID for the agent contract */
agentContractId?: string;
/** Sponsor account configuration for funding the agent account */
sponsor?: {
/** The sponsor's NEAR account ID */
accountId: string;
/** The sponsor's private key */
privateKey: string;
};
/** Custom NEAR RPC provider. If not provided, a default provider will be created based on networkId */
rpc?: Provider;
/** Number of keys to use for the agent (1-100), defaults to 1 if not provided */
numKeys?: number;
/** Derivation path for deterministic key generation for local testing (needs to be a randomly unique string)*/
derivationPath?: string;
}
declare class ShadeClient {
private config;
private dstackClient;
private agentAccountId;
private agentPrivateKeys;
private currentKeyIndex;
private keysDerivedWithRandom;
private keysChecked;
private constructor();
/**
* Creates a new ShadeClient instance asynchronously
* @param config - Configuration object for the Shade client (see ShadeConfig interface for details)
* @returns Promise that resolves to a ShadeClient instance
* @throws Error if configuration is invalid, network ID mismatch, or key generation fails
*/
static create(config: ShadeConfig): Promise<ShadeClient>;
/**
* Gets the NEAR account ID of the agent
* @returns The agent's account ID
*/
accountId(): string;
/**
* Gets the NEAR balance of the agent account in human readable format (e.g. 1 = one NEAR)
* @returns Promise that resolves to the account balance in NEAR tokens, if the agent account does not exist, returns 0
* @throws Error if network request fails
*/
balance(): Promise<number>;
/**
* Registers the agent in the agent contract.
*
* @param params
* @param params.deposit Attached deposit in yoctoNEAR when storage is required or when `forceDeposit` is `true` (defaults to `5000000000000000000000` — 0.005 NEAR)
* @param params.forceDeposit If `true`, always attach `deposit` (or the default) and skip `get_agent`. If `false`, attach no deposit and skip `get_agent`. If omitted, use `get_agent` to decide.
* @returns Promise that resolves to true if registration was successful
* @throws Error if agentContractId is not configured, if fetching attestation fails, or if the contract call fails
*/
register(params?: {
deposit?: bigint | string | number;
forceDeposit?: boolean;
}): Promise<boolean>;
/**
* Call a view function on the agent contract and return the result
* @param params
* @param params.methodName The method that will be called
* @param params.args Arguments as a valid JSON Object
* @param params.blockQuery (optional) Block reference for the query
* @returns A promise that resolves with the result of the view function call
* @throws Error if agentContractId is not configured or if RPC call fails
*/
view<T extends SerializedReturnValue>(params: {
methodName: string;
args: Record<string, unknown>;
blockQuery?: BlockReference;
}): Promise<T>;
/**
* Call a function on the agent contract and return the result
* @param params
* @param params.methodName The method that will be called
* @param params.args Arguments, either as a valid JSON Object or a raw Uint8Array
* @param params.deposit (optional) Amount of NEAR Tokens to attach to the call
* @param params.gas (optional) Amount of GAS to use attach to the call
* @param params.waitUntil (optional) Transaction finality to wait for
* @returns A promise that resolves with the result of the contract function call
* @throws Error if agentContractId is not configured, if key derivation fails, or if transaction fails
*/
call<T extends SerializedReturnValue>(params: {
methodName: string;
args: Uint8Array | Record<string, any>;
deposit?: bigint | string | number;
gas?: bigint | string | number;
waitUntil?: TxExecutionStatus;
}): Promise<T>;
/**
* Gets the TEE attestation for the agent in contract format (ready to be sent to the contract)
* @returns Promise that resolves to the contract-formatted attestation object
* @throws Error if fetching quote collateral fails (network errors, HTTP errors, timeouts)
*/
getAttestation(): Promise<DstackAttestationForContract>;
/**
* Funds the agent account with NEAR tokens from the sponsor account
* @param fundAmount - Amount of NEAR tokens to transfer to the agent account in human readable format (e.g. 1 = one NEAR)
* @returns Promise that resolves when funding is complete
* @throws Error if sponsor is not configured or if transfer fails after retries
*/
fund(fundAmount: number): Promise<void>;
/**
* Gets the agent's private keys (use with caution)
* @param params - Must pass `{ acknowledgeRisk: true }`
* @returns Array of private key strings
*/
getPrivateKeys(params: {
acknowledgeRisk: true;
}): string[];
/**
* Checks if the agent is whitelisted for local mode
* @returns Promise that resolves to true if the agent is whitelisted, false if the agent is not whitelisted, or null if the agent contract requires TEE
* @throws Error if agentContractId is not configured or if view call fails
*/
isWhitelisted(): Promise<boolean | null>;
}
/**
* Error / sanitisation utilities. The single trust boundary for secret leak
* prevention in shade-agent-js: every catch in the package should rethrow
* via `toThrowable(e)` so that sensitive fields and recognised secret value
* patterns are redacted before the error escapes.
*
* Fail-closed rule: if any value can't be safely processed (hostile getter,
* throwing Proxy, deep-redact crash, …), it is replaced with the
* `[unsanitisable]` placeholder string so operators can see that
* something was there but couldn't be sanitised. The unsanitised
* original is NEVER propagated. The only exception is the final
* `toThrowable` boundary, which must return a real Error — there it
* falls back to a generic "An error occurred" message.
*
* File layout — six sections, top to bottom:
* 1. Redaction policy — which field names + value patterns count as secrets.
* 2. Redactor engine — the mutable deep-redact instance + extension API.
* 3. Sanitiser — walks any value, redacts secrets, marks unsanitisables.
* 4. Public throw API — toThrowable + genericError.
* 5. Retry — defaultRetryable + withRetry + helpers.
* 6. Safe key parsing — safeParseKeyPair / safeParseSigner.
*/
/** Shape of a value-pattern entry: a regex to test, plus a replacer function. */
interface StringTest {
pattern: RegExp;
replacer: (value: string, pattern: RegExp) => string;
}
/**
* Extend the redaction config at runtime. After this returns, subsequent
* sanitize/toThrowable calls also redact the new field names and apply
* the new value patterns. Mutation is process-global — call once at boot.
*/
declare function addSensitive(opts: {
keys?: string[];
patterns?: StringTest[];
}): void;
/**
* Sanitise any value. Type-routed:
* - primitives (number, boolean, bigint, symbol, null, undefined)
* → returned as-is (nothing to redact)
* - string → value-pattern redaction applied
* - Error → routed through `sanitizeError`
* - object → deep-redact walks string keys; symbol-keyed properties are dropped
*
* Marks on uncertainty: if the value is so exotic the redactor can't
* process it (hostile Proxy, throwing prototype trap), returns the
* `"[unsanitisable]"` placeholder string instead of the original value.
* The unsanitised value is never propagated.
*/
declare function sanitize(value: unknown): unknown;
/**
* Take any thrown value, return a safe Error to rethrow.
*
* try { ... } catch (e) { throw toThrowable(e); }
*
* Never throws, even on circular structures, BigInt
* values, hostile Proxies, or other exotic shapes. If sanitisation
* dropped the input entirely, returns `new Error("An error occurred")`
* rather than echoing anything potentially unsafe.
*/
declare function toThrowable(error: unknown): Error;
export { type DstackAttestationForContract, type FullMeasurements, type Measurements, ShadeClient, type ShadeConfig, addSensitive, sanitize, toThrowable };