diff --git a/ceps-client-wasm-schema/pkg-nodejs/README.md b/ceps-client-wasm-schema/pkg-nodejs/README.md new file mode 100644 index 0000000..f88ab12 --- /dev/null +++ b/ceps-client-wasm-schema/pkg-nodejs/README.md @@ -0,0 +1,320 @@ +# ceps-rust-ts-client + +One **Rust** client for Casper **CEP-18**, **CEP-78**, **CEP-85**, and **CEP-95**, with a **`ceps-client-cli`**, **`ceps-rust-ts-client-mcp`**, and **WASM packs** so the same client can run from shell, agents, Node, or the browser. + +It replaces the separate TypeScript **`client-js`** packages that lived next to each CEP contract. Instead of per-CEP JS clients, you use one library: `CEP18Client` / `CEP78Client` / `CEP85Client` / `CEP95Client`, on top of [`casper-rust-wasm-sdk`](https://github.com/casper-ecosystem/casper-rust-wasm-sdk). + +```text +CEP-18 client-js ─┐ +CEP-78 client-js ─┼─→ ceps-client (Rust) + ceps-client-cli + ceps-rust-ts-client-mcp + ceps-client-wasm +CEP-85 client-js ─┤ +CEP-95 JS client ─┘ +``` + +## What you get + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PieceNameRoleIn-tree path
Rust libraryceps‑clientCEP API for native Rust appsceps-client/
CLIceps‑client‑cliStatus and common queries from the shellceps-client-cli/
MCP serverceps-rust-ts-client-mcpFull CEP agent tools (stdio / HTTP :6790)mcp/
Client JS packsceps‑client‑wasmSame CEP classes for Node and browsers (replaces per-CEP client-js)ceps-client-wasm/pkg / pkg-nodejs
Demo contract WASMsceps‑contractsOn-chain bytecode for install (demo tips)tests/wasm/{cep18,cep78,cep85,cep95}/
+ +Release downloads use the same names: `ceps-client-cli-*-linux-x86_64`, `ceps-rust-ts-client-mcp-*-linux-x86_64`, `ceps-client-wasm-*.tgz`, and `ceps-contracts-*.tgz`. The library / CLI / MCP / JS packs are this client; `ceps-contracts` is sample contract bytecode, not the JS library. + +## What you can do + +| CEP | Standard | Typical flow | +| ------ | -------------- | -------------------------------------------------------------------------- | +| **18** | Fungible token | install → bind hash → `name` / `balance_of` → `transfer` / `mint` / `burn` | +| **78** | Enhanced NFT | install → bind hash → `mint` → `owner_of` / `balance_of` | +| **85** | Multi-token | install → bind hash → `mint` / `burn` → `balance_of(account, id)` | +| **95** | NFT (Odra tip) | install → `bind_odra_install` → `mint` → `owner_of` / `approve` | + +Defaults talk to local NCTL (`http://127.0.0.1:11101`, SSE `…:18101/events`, chain `casper-net-1`). + +## Usage + +Needs a running node, a secret-key PEM, and on-chain contract `.wasm` bytes (your own build, or the [demo tips](#contract-wasms-demos) via `make wasm-from-ceps`). + +### CEP-18 - fungible + +```text +install → named keys cep18_contract_hash_* / package_* + → set_contract_hash + → name / symbol / balance_of + → transfer | mint | burn +``` + +```rust +use ceps_client::cep18::InstallArgs; +use ceps_client::{CEP18Client, TransactionParams, EventsMode, Verbosity}; + +let mut client = CEP18Client::new( + "http://127.0.0.1:11101", + Some("http://127.0.0.1:18101/events".into()), + Some("casper-net-1".into()), + Some(Verbosity::Low), +)?; + +let put = client + .install( + &InstallArgs::new("MyToken", "MTK", 9, "1000000000") + .with_events_mode(EventsMode::CES) + .with_mint_and_burn(true), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "400000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +let bal = client.balance_of("account-hash-…").await?; +``` + +Details: [docs/cep18/](docs/cep18/) · example: `cargo run -p ceps-client --example cep18_install` + +### CEP-78 - NFT + +```text +install → named keys cep78_contract_hash_* / package_* + → set_contract_hash + → mint → owner_of / balance_of +``` + +```rust +use ceps_client::cep78::InstallArgs; +use ceps_client::{CEP78Client, TransactionParams, EventsMode78, Verbosity}; + +let mut client = CEP78Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyNft", "NFT", 100).with_events_mode(EventsMode78::CES), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "600000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +client + .mint( + "account-hash-…", + r#"{"name":"token-1"}"#, + None, + &TransactionParams::new(&secret_pem, "5000000000"), + ) + .await?; +let owner = client.owner_of(&token_id).await?; +``` + +Details: [docs/cep78/](docs/cep78/) · example: `cargo run -p ceps-client --example cep78_install` + +### CEP-85 - multi-token + +```text +install → named keys cep85_contract_hash_* / package_* + → set_contract_hash + → mint / burn → balance_of(account, id) +``` + +```rust +use ceps_client::cep85::InstallArgs; +use ceps_client::{CEP85Client, TransactionParams, EventsMode, Verbosity}; + +let mut client = CEP85Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyMulti", "https://example.com/{id}.json") + .with_events_mode(EventsMode::CES) + .with_enable_burn(true), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "550000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +client.mint(&owner, "1", "10", None, &TransactionParams::new(&secret_pem, "5000000000")).await?; +let bal = client.balance_of(&owner, "1").await?; +``` + +Details: [docs/cep85/](docs/cep85/) · example: `cargo run -p ceps-client --example cep85_install` + +### CEP-95 - NFT (Odra tip) + +```text +install (odra_cfg_package_hash_key_name) → bind_odra_install + → name / symbol / balance_of / owner_of + → mint | burn | transfer_from | approve* +``` + +```rust +use ceps_client::cep95::InstallArgs; +use ceps_client::{CEP95Client, TransactionParams, Verbosity}; + +let mut client = CEP95Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyNft", "MNFT", "cep95_pkg_demo"), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "600000000000"), + ) + .await?; +client.bind_odra_install(&installer_public_key, "cep95_pkg_demo").await?; +client.mint(&owner, "1", None, &TransactionParams::new(&secret_pem, "5000000000")).await?; +let owner_of = client.owner_of("1").await?; +``` + +Details: [docs/cep95/](docs/cep95/) · example: `cargo run -p ceps-client --example cep95_install` + +### CLI + +```bash +cargo run -p ceps-client-cli -- status +cargo run -p ceps-client-cli -- cep18 info +cargo run -p ceps-client-cli -- cep78 balance --contract-hash --account +cargo run -p ceps-client-cli -- cep85 balance --contract-hash --account <…> --id 1 +cargo run -p ceps-client-cli -- cep95 owner-of --contract-hash --token-id 1 +``` + +Mutations are on the library / examples today. Flags: [docs/cli.md](docs/cli.md). + +### Try an install end-to-end + +```bash +make prepare && make build +make wasm-from-ceps # copy demo tip contract WASMs into tests/wasm/ +# NCTL running + SECRET_KEY_USER_1 set to a PEM: +cargo run -p ceps-client --example cep18_install +``` + +More setup: [docs/getting-started.md](docs/getting-started.md). + +## Client JS packs (`ceps-client-wasm`) + +`ceps-client-wasm` is the **Rust CEP client compiled for JavaScript** (the replacement for per-CEP `client-js`). Use it from Node or the browser for the same `CEP18Client` / `CEP78Client` / `CEP85Client` surface. + +It is **not** on-chain contract bytecode (that is `tests/wasm/` / `ceps-contracts-*.tgz`). + +| Target | In-tree (committed) | Typical use | +| ------ | ------------------------------ | -------------------------- | +| Node | `ceps-client-wasm/pkg-nodejs/` | Backend / scripts / Vitest | +| Web | `ceps-client-wasm/pkg/` | Bundled frontends | + +Rebuild locally with `make pack` (or `make nodejs` / `make web`). Details: [docs/wasm-ts.md](docs/wasm-ts.md) · [docs/releases.md](docs/releases.md). + +**Or** fetch a published pack (no local `wasm-pack`): + +```bash +TAG=v1.0.0 # or dev-preview +LABEL=${TAG#v} +curl -fsSL -o ceps-client-wasm-nodejs.tgz \ + "https://github.com/Interchouette-ITC/ceps-rust-ts-client/releases/download/${TAG}/ceps-client-wasm-nodejs-${LABEL}.tgz" +mkdir -p ceps-client-wasm && tar -xzf ceps-client-wasm-nodejs.tgz -C ceps-client-wasm +# -> ceps-client-wasm/pkg-nodejs/ +``` + +```js +import { CEP18Client } from "ceps-client-wasm"; // file:./ceps-client-wasm/pkg-nodejs after unpack + +const client = new CEP18Client( + "http://127.0.0.1:11101", + "http://127.0.0.1:18101/events", + "casper-net-1", + 0, +); +client.setContractHash(contractHash, packageHash); +const name = await client.name(); +const bal = await client.balanceOf("account-hash-…"); +``` + +Install from JS takes contract bytes as `Uint8Array` and returns JSON `{ transactionHash, hasExecutionResult }`. Bound methods today are a subset of the Rust API (see [docs/wasm-ts.md](docs/wasm-ts.md)); full parity is on `ceps-client`. + +## Contract WASMs (demos) + +This client is **not** a contract repo. You pass on-chain `.wasm` into `install`. + +**In-tree:** the last staged demo tip builds ship under `tests/wasm/{cep18,cep78,cep85}/` (same idea as committing `pkg` / `pkg-nodejs`). Use those bytes directly, or refresh with `make wasm-from-ceps`. + +**Or** download the release bundle (no tip checkout / no contract build): + +```bash +TAG=v1.0.0 +LABEL=${TAG#v} +curl -fsSL -o ceps-contracts.tgz \ + "https://github.com/Interchouette-ITC/ceps-rust-ts-client/releases/download/${TAG}/ceps-contracts-${LABEL}.tgz" +mkdir -p tests/wasm && tar -xzf ceps-contracts.tgz -C tests/wasm +``` + +Tips are short-lived entity-era builds for demos/CI, not a claim of "the" upstream CEP tip forever. Sources: + +| CEP | Demo tip repo | Branch | What you get | +| --- | ------------- | ------ | ------------ | +| 18 | [Interchouette-ITC/cep-18](https://github.com/Interchouette-ITC/cep-18) | `ceps-client-test` | Fungible contract WASM | +| 78 | [Interchouette-ITC/cep-78-enhanced-nft](https://github.com/Interchouette-ITC/cep-78-enhanced-nft) | `ceps-client-test` | NFT + session WASMs | +| 85 | [Interchouette-ITC/cep-85](https://github.com/Interchouette-ITC/cep-85) | `ceps-client-test` | Multi-token WASM | + +```bash +# after checking out those tips and building contracts there: +make wasm-from-ceps # → tests/wasm/{cep18,cep78,cep85}/ +``` + +Override checkout roots with `CEP18_PRODUCT` / `CEP78_PRODUCT` / `CEP85_PRODUCT`. Pins and SHAs: [docs/contributing.md](docs/contributing.md). All release downloads: [docs/releases.md](docs/releases.md). + +## Documentation + +| Doc | Description | +| ------------------------------------------------------------------------------------------------------------ | ----------------------------------- | +| [Getting started](docs/getting-started.md) | Build, NCTL defaults, first run | +| [Architecture](docs/architecture.md) | How lib / CLI / WASM sit on the SDK | +| [docs/cep18/](docs/cep18/) · [cep78/](docs/cep78/) · [cep85/](docs/cep85/) | Per-CEP guides | +| [CLI](docs/cli.md) · [WASM / TS](docs/wasm-ts.md) | Surfaces | +| [Testing](docs/testing.md) · [CI / CD](docs/ci.md) · [Releases](docs/releases.md) · [Docker](docs/docker.md) | Verify and ship | +| [Contributing](docs/contributing.md) · [SDK](docs/sdk.md) | Tips, pins, upgrades | +| [SECURITY.md](docs/SECURITY.md) | Keys and reporting | +| `make doc` | rustdoc → `docs/api-rust/` | + +## Docker + +```bash +make release-cli-bin && make docker-build IMAGE_TAG=local +docker run --rm ceps-rust-ts-client:local --help +docker pull interchouette/ceps-rust-ts-client:dev +``` + +See [docs/docker.md](docs/docker.md). + +## License / security + +GPL-3.0. See [LICENSE](LICENSE) and [docs/SECURITY.md](docs/SECURITY.md). diff --git a/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.d.ts b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.d.ts new file mode 100644 index 0000000..9a99391 --- /dev/null +++ b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.d.ts @@ -0,0 +1,32 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * CEP-18 schema JSON. + */ +export function cep18SchemaJson(): string; + +/** + * CEP-78 schema JSON. + */ +export function cep78SchemaJson(): string; + +/** + * CEP-85 schema JSON. + */ +export function cep85SchemaJson(): string; + +/** + * CEP-95 schema JSON. + */ +export function cep95SchemaJson(): string; + +/** + * JSON schema for a CEP id (`cep18`, `cep78`, `cep85`, `cep95`). + */ +export function schemaJson(cep: string): string; + +/** + * Supported CEP id strings. + */ +export function supportedCeps(): any; diff --git a/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.js b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.js new file mode 100644 index 0000000..40852aa --- /dev/null +++ b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm.js @@ -0,0 +1,241 @@ +/* @ts-self-types="./ceps_client_wasm.d.ts" */ + +/** + * CEP-18 schema JSON. + * @returns {string} + */ +function cep18SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep18SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} +exports.cep18SchemaJson = cep18SchemaJson; + +/** + * CEP-78 schema JSON. + * @returns {string} + */ +function cep78SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep78SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} +exports.cep78SchemaJson = cep78SchemaJson; + +/** + * CEP-85 schema JSON. + * @returns {string} + */ +function cep85SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep85SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} +exports.cep85SchemaJson = cep85SchemaJson; + +/** + * CEP-95 schema JSON. + * @returns {string} + */ +function cep95SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep95SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} +exports.cep95SchemaJson = cep95SchemaJson; + +/** + * JSON schema for a CEP id (`cep18`, `cep78`, `cep85`, `cep95`). + * @param {string} cep + * @returns {string} + */ +function schemaJson(cep) { + let deferred3_0; + let deferred3_1; + try { + const ptr0 = passStringToWasm0(cep, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.schemaJson(ptr0, len0); + var ptr2 = ret[0]; + var len2 = ret[1]; + if (ret[3]) { + ptr2 = 0; len2 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); + } +} +exports.schemaJson = schemaJson; + +/** + * Supported CEP id strings. + * @returns {any} + */ +function supportedCeps() { + const ret = wasm.supportedCeps(); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} +exports.supportedCeps = supportedCeps; +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_bb96b2010945f0bc: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_parse_1cc93481b0865939: function() { return handleError(function (arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); + return ret; + }, arguments); }, + __wbindgen_cast_0000000000000001: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./ceps_client_wasm_bg.js": import0, + }; +} + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; +} + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +function decodeText(ptr, len) { + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; +} + +let WASM_VECTOR_LEN = 0; + +const wasmPath = `${__dirname}/ceps_client_wasm_bg.wasm`; +const wasmBytes = require('fs').readFileSync(wasmPath); +const wasmModule = new WebAssembly.Module(wasmBytes); +let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports()); +let wasm = wasmInstance.exports; +wasm.__wbindgen_start(); diff --git a/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm new file mode 100644 index 0000000..54c5e08 Binary files /dev/null and b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm differ diff --git a/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm.d.ts b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm.d.ts new file mode 100644 index 0000000..4c995f3 --- /dev/null +++ b/ceps-client-wasm-schema/pkg-nodejs/ceps_client_wasm_bg.wasm.d.ts @@ -0,0 +1,17 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const cep18SchemaJson: () => [number, number]; +export const cep78SchemaJson: () => [number, number]; +export const cep85SchemaJson: () => [number, number]; +export const cep95SchemaJson: () => [number, number]; +export const schemaJson: (a: number, b: number) => [number, number, number, number]; +export const supportedCeps: () => [number, number, number]; +export const __wbindgen_exn_store: (a: number) => void; +export const __externref_table_alloc: () => number; +export const __wbindgen_externrefs: WebAssembly.Table; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __externref_table_dealloc: (a: number) => void; +export const __wbindgen_start: () => void; diff --git a/ceps-client-wasm-schema/pkg-nodejs/package.json b/ceps-client-wasm-schema/pkg-nodejs/package.json new file mode 100644 index 0000000..6f3d74c --- /dev/null +++ b/ceps-client-wasm-schema/pkg-nodejs/package.json @@ -0,0 +1,21 @@ +{ + "name": "ceps-client-wasm-schema-nodejs", + "collaborators": [ + "3099551+gRoussac@users.noreply.github.com" + ], + "description": "wasm-bindgen CEP client for Node and browsers (same API as ceps-client; replaces per-CEP client-js).", + "version": "1.0.0", + "license": "SEE LICENSE IN ../LICENSE", + "repository": { + "type": "git", + "url": "https://github.com/Interchouette-ITC/ceps-rust-ts-client" + }, + "files": [ + "ceps_client_wasm_bg.wasm", + "ceps_client_wasm.js", + "ceps_client_wasm.d.ts" + ], + "main": "ceps_client_wasm.js", + "homepage": "https://github.com/Interchouette-ITC/ceps-rust-ts-client", + "types": "ceps_client_wasm.d.ts" +} diff --git a/ceps-client-wasm-schema/pkg/README.md b/ceps-client-wasm-schema/pkg/README.md new file mode 100644 index 0000000..f88ab12 --- /dev/null +++ b/ceps-client-wasm-schema/pkg/README.md @@ -0,0 +1,320 @@ +# ceps-rust-ts-client + +One **Rust** client for Casper **CEP-18**, **CEP-78**, **CEP-85**, and **CEP-95**, with a **`ceps-client-cli`**, **`ceps-rust-ts-client-mcp`**, and **WASM packs** so the same client can run from shell, agents, Node, or the browser. + +It replaces the separate TypeScript **`client-js`** packages that lived next to each CEP contract. Instead of per-CEP JS clients, you use one library: `CEP18Client` / `CEP78Client` / `CEP85Client` / `CEP95Client`, on top of [`casper-rust-wasm-sdk`](https://github.com/casper-ecosystem/casper-rust-wasm-sdk). + +```text +CEP-18 client-js ─┐ +CEP-78 client-js ─┼─→ ceps-client (Rust) + ceps-client-cli + ceps-rust-ts-client-mcp + ceps-client-wasm +CEP-85 client-js ─┤ +CEP-95 JS client ─┘ +``` + +## What you get + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PieceNameRoleIn-tree path
Rust libraryceps‑clientCEP API for native Rust appsceps-client/
CLIceps‑client‑cliStatus and common queries from the shellceps-client-cli/
MCP serverceps-rust-ts-client-mcpFull CEP agent tools (stdio / HTTP :6790)mcp/
Client JS packsceps‑client‑wasmSame CEP classes for Node and browsers (replaces per-CEP client-js)ceps-client-wasm/pkg / pkg-nodejs
Demo contract WASMsceps‑contractsOn-chain bytecode for install (demo tips)tests/wasm/{cep18,cep78,cep85,cep95}/
+ +Release downloads use the same names: `ceps-client-cli-*-linux-x86_64`, `ceps-rust-ts-client-mcp-*-linux-x86_64`, `ceps-client-wasm-*.tgz`, and `ceps-contracts-*.tgz`. The library / CLI / MCP / JS packs are this client; `ceps-contracts` is sample contract bytecode, not the JS library. + +## What you can do + +| CEP | Standard | Typical flow | +| ------ | -------------- | -------------------------------------------------------------------------- | +| **18** | Fungible token | install → bind hash → `name` / `balance_of` → `transfer` / `mint` / `burn` | +| **78** | Enhanced NFT | install → bind hash → `mint` → `owner_of` / `balance_of` | +| **85** | Multi-token | install → bind hash → `mint` / `burn` → `balance_of(account, id)` | +| **95** | NFT (Odra tip) | install → `bind_odra_install` → `mint` → `owner_of` / `approve` | + +Defaults talk to local NCTL (`http://127.0.0.1:11101`, SSE `…:18101/events`, chain `casper-net-1`). + +## Usage + +Needs a running node, a secret-key PEM, and on-chain contract `.wasm` bytes (your own build, or the [demo tips](#contract-wasms-demos) via `make wasm-from-ceps`). + +### CEP-18 - fungible + +```text +install → named keys cep18_contract_hash_* / package_* + → set_contract_hash + → name / symbol / balance_of + → transfer | mint | burn +``` + +```rust +use ceps_client::cep18::InstallArgs; +use ceps_client::{CEP18Client, TransactionParams, EventsMode, Verbosity}; + +let mut client = CEP18Client::new( + "http://127.0.0.1:11101", + Some("http://127.0.0.1:18101/events".into()), + Some("casper-net-1".into()), + Some(Verbosity::Low), +)?; + +let put = client + .install( + &InstallArgs::new("MyToken", "MTK", 9, "1000000000") + .with_events_mode(EventsMode::CES) + .with_mint_and_burn(true), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "400000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +let bal = client.balance_of("account-hash-…").await?; +``` + +Details: [docs/cep18/](docs/cep18/) · example: `cargo run -p ceps-client --example cep18_install` + +### CEP-78 - NFT + +```text +install → named keys cep78_contract_hash_* / package_* + → set_contract_hash + → mint → owner_of / balance_of +``` + +```rust +use ceps_client::cep78::InstallArgs; +use ceps_client::{CEP78Client, TransactionParams, EventsMode78, Verbosity}; + +let mut client = CEP78Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyNft", "NFT", 100).with_events_mode(EventsMode78::CES), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "600000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +client + .mint( + "account-hash-…", + r#"{"name":"token-1"}"#, + None, + &TransactionParams::new(&secret_pem, "5000000000"), + ) + .await?; +let owner = client.owner_of(&token_id).await?; +``` + +Details: [docs/cep78/](docs/cep78/) · example: `cargo run -p ceps-client --example cep78_install` + +### CEP-85 - multi-token + +```text +install → named keys cep85_contract_hash_* / package_* + → set_contract_hash + → mint / burn → balance_of(account, id) +``` + +```rust +use ceps_client::cep85::InstallArgs; +use ceps_client::{CEP85Client, TransactionParams, EventsMode, Verbosity}; + +let mut client = CEP85Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyMulti", "https://example.com/{id}.json") + .with_events_mode(EventsMode::CES) + .with_enable_burn(true), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "550000000000"), + ) + .await?; +client.set_contract_hash(&contract_hash, Some(&package_hash))?; +client.mint(&owner, "1", "10", None, &TransactionParams::new(&secret_pem, "5000000000")).await?; +let bal = client.balance_of(&owner, "1").await?; +``` + +Details: [docs/cep85/](docs/cep85/) · example: `cargo run -p ceps-client --example cep85_install` + +### CEP-95 - NFT (Odra tip) + +```text +install (odra_cfg_package_hash_key_name) → bind_odra_install + → name / symbol / balance_of / owner_of + → mint | burn | transfer_from | approve* +``` + +```rust +use ceps_client::cep95::InstallArgs; +use ceps_client::{CEP95Client, TransactionParams, Verbosity}; + +let mut client = CEP95Client::new(/* rpc, sse, chain, verbosity */)?; +client + .install( + &InstallArgs::new("MyNft", "MNFT", "cep95_pkg_demo"), + &contract_wasm_bytes, + &TransactionParams::new(&secret_pem, "600000000000"), + ) + .await?; +client.bind_odra_install(&installer_public_key, "cep95_pkg_demo").await?; +client.mint(&owner, "1", None, &TransactionParams::new(&secret_pem, "5000000000")).await?; +let owner_of = client.owner_of("1").await?; +``` + +Details: [docs/cep95/](docs/cep95/) · example: `cargo run -p ceps-client --example cep95_install` + +### CLI + +```bash +cargo run -p ceps-client-cli -- status +cargo run -p ceps-client-cli -- cep18 info +cargo run -p ceps-client-cli -- cep78 balance --contract-hash --account +cargo run -p ceps-client-cli -- cep85 balance --contract-hash --account <…> --id 1 +cargo run -p ceps-client-cli -- cep95 owner-of --contract-hash --token-id 1 +``` + +Mutations are on the library / examples today. Flags: [docs/cli.md](docs/cli.md). + +### Try an install end-to-end + +```bash +make prepare && make build +make wasm-from-ceps # copy demo tip contract WASMs into tests/wasm/ +# NCTL running + SECRET_KEY_USER_1 set to a PEM: +cargo run -p ceps-client --example cep18_install +``` + +More setup: [docs/getting-started.md](docs/getting-started.md). + +## Client JS packs (`ceps-client-wasm`) + +`ceps-client-wasm` is the **Rust CEP client compiled for JavaScript** (the replacement for per-CEP `client-js`). Use it from Node or the browser for the same `CEP18Client` / `CEP78Client` / `CEP85Client` surface. + +It is **not** on-chain contract bytecode (that is `tests/wasm/` / `ceps-contracts-*.tgz`). + +| Target | In-tree (committed) | Typical use | +| ------ | ------------------------------ | -------------------------- | +| Node | `ceps-client-wasm/pkg-nodejs/` | Backend / scripts / Vitest | +| Web | `ceps-client-wasm/pkg/` | Bundled frontends | + +Rebuild locally with `make pack` (or `make nodejs` / `make web`). Details: [docs/wasm-ts.md](docs/wasm-ts.md) · [docs/releases.md](docs/releases.md). + +**Or** fetch a published pack (no local `wasm-pack`): + +```bash +TAG=v1.0.0 # or dev-preview +LABEL=${TAG#v} +curl -fsSL -o ceps-client-wasm-nodejs.tgz \ + "https://github.com/Interchouette-ITC/ceps-rust-ts-client/releases/download/${TAG}/ceps-client-wasm-nodejs-${LABEL}.tgz" +mkdir -p ceps-client-wasm && tar -xzf ceps-client-wasm-nodejs.tgz -C ceps-client-wasm +# -> ceps-client-wasm/pkg-nodejs/ +``` + +```js +import { CEP18Client } from "ceps-client-wasm"; // file:./ceps-client-wasm/pkg-nodejs after unpack + +const client = new CEP18Client( + "http://127.0.0.1:11101", + "http://127.0.0.1:18101/events", + "casper-net-1", + 0, +); +client.setContractHash(contractHash, packageHash); +const name = await client.name(); +const bal = await client.balanceOf("account-hash-…"); +``` + +Install from JS takes contract bytes as `Uint8Array` and returns JSON `{ transactionHash, hasExecutionResult }`. Bound methods today are a subset of the Rust API (see [docs/wasm-ts.md](docs/wasm-ts.md)); full parity is on `ceps-client`. + +## Contract WASMs (demos) + +This client is **not** a contract repo. You pass on-chain `.wasm` into `install`. + +**In-tree:** the last staged demo tip builds ship under `tests/wasm/{cep18,cep78,cep85}/` (same idea as committing `pkg` / `pkg-nodejs`). Use those bytes directly, or refresh with `make wasm-from-ceps`. + +**Or** download the release bundle (no tip checkout / no contract build): + +```bash +TAG=v1.0.0 +LABEL=${TAG#v} +curl -fsSL -o ceps-contracts.tgz \ + "https://github.com/Interchouette-ITC/ceps-rust-ts-client/releases/download/${TAG}/ceps-contracts-${LABEL}.tgz" +mkdir -p tests/wasm && tar -xzf ceps-contracts.tgz -C tests/wasm +``` + +Tips are short-lived entity-era builds for demos/CI, not a claim of "the" upstream CEP tip forever. Sources: + +| CEP | Demo tip repo | Branch | What you get | +| --- | ------------- | ------ | ------------ | +| 18 | [Interchouette-ITC/cep-18](https://github.com/Interchouette-ITC/cep-18) | `ceps-client-test` | Fungible contract WASM | +| 78 | [Interchouette-ITC/cep-78-enhanced-nft](https://github.com/Interchouette-ITC/cep-78-enhanced-nft) | `ceps-client-test` | NFT + session WASMs | +| 85 | [Interchouette-ITC/cep-85](https://github.com/Interchouette-ITC/cep-85) | `ceps-client-test` | Multi-token WASM | + +```bash +# after checking out those tips and building contracts there: +make wasm-from-ceps # → tests/wasm/{cep18,cep78,cep85}/ +``` + +Override checkout roots with `CEP18_PRODUCT` / `CEP78_PRODUCT` / `CEP85_PRODUCT`. Pins and SHAs: [docs/contributing.md](docs/contributing.md). All release downloads: [docs/releases.md](docs/releases.md). + +## Documentation + +| Doc | Description | +| ------------------------------------------------------------------------------------------------------------ | ----------------------------------- | +| [Getting started](docs/getting-started.md) | Build, NCTL defaults, first run | +| [Architecture](docs/architecture.md) | How lib / CLI / WASM sit on the SDK | +| [docs/cep18/](docs/cep18/) · [cep78/](docs/cep78/) · [cep85/](docs/cep85/) | Per-CEP guides | +| [CLI](docs/cli.md) · [WASM / TS](docs/wasm-ts.md) | Surfaces | +| [Testing](docs/testing.md) · [CI / CD](docs/ci.md) · [Releases](docs/releases.md) · [Docker](docs/docker.md) | Verify and ship | +| [Contributing](docs/contributing.md) · [SDK](docs/sdk.md) | Tips, pins, upgrades | +| [SECURITY.md](docs/SECURITY.md) | Keys and reporting | +| `make doc` | rustdoc → `docs/api-rust/` | + +## Docker + +```bash +make release-cli-bin && make docker-build IMAGE_TAG=local +docker run --rm ceps-rust-ts-client:local --help +docker pull interchouette/ceps-rust-ts-client:dev +``` + +See [docs/docker.md](docs/docker.md). + +## License / security + +GPL-3.0. See [LICENSE](LICENSE) and [docs/SECURITY.md](docs/SECURITY.md). diff --git a/ceps-client-wasm-schema/pkg/ceps_client_wasm.d.ts b/ceps-client-wasm-schema/pkg/ceps_client_wasm.d.ts new file mode 100644 index 0000000..4d3a342 --- /dev/null +++ b/ceps-client-wasm-schema/pkg/ceps_client_wasm.d.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * CEP-18 schema JSON. + */ +export function cep18SchemaJson(): string; + +/** + * CEP-78 schema JSON. + */ +export function cep78SchemaJson(): string; + +/** + * CEP-85 schema JSON. + */ +export function cep85SchemaJson(): string; + +/** + * CEP-95 schema JSON. + */ +export function cep95SchemaJson(): string; + +/** + * JSON schema for a CEP id (`cep18`, `cep78`, `cep85`, `cep95`). + */ +export function schemaJson(cep: string): string; + +/** + * Supported CEP id strings. + */ +export function supportedCeps(): any; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly cep18SchemaJson: () => [number, number]; + readonly cep78SchemaJson: () => [number, number]; + readonly cep85SchemaJson: () => [number, number]; + readonly cep95SchemaJson: () => [number, number]; + readonly schemaJson: (a: number, b: number) => [number, number, number, number]; + readonly supportedCeps: () => [number, number, number]; + readonly __wbindgen_exn_store: (a: number) => void; + readonly __externref_table_alloc: () => number; + readonly __wbindgen_externrefs: WebAssembly.Table; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __externref_table_dealloc: (a: number) => void; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; + +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/ceps-client-wasm-schema/pkg/ceps_client_wasm.js b/ceps-client-wasm-schema/pkg/ceps_client_wasm.js new file mode 100644 index 0000000..3b01b37 --- /dev/null +++ b/ceps-client-wasm-schema/pkg/ceps_client_wasm.js @@ -0,0 +1,333 @@ +/* @ts-self-types="./ceps_client_wasm.d.ts" */ + +/** + * CEP-18 schema JSON. + * @returns {string} + */ +export function cep18SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep18SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} + +/** + * CEP-78 schema JSON. + * @returns {string} + */ +export function cep78SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep78SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} + +/** + * CEP-85 schema JSON. + * @returns {string} + */ +export function cep85SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep85SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} + +/** + * CEP-95 schema JSON. + * @returns {string} + */ +export function cep95SchemaJson() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.cep95SchemaJson(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } +} + +/** + * JSON schema for a CEP id (`cep18`, `cep78`, `cep85`, `cep95`). + * @param {string} cep + * @returns {string} + */ +export function schemaJson(cep) { + let deferred3_0; + let deferred3_1; + try { + const ptr0 = passStringToWasm0(cep, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.schemaJson(ptr0, len0); + var ptr2 = ret[0]; + var len2 = ret[1]; + if (ret[3]) { + ptr2 = 0; len2 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); + } +} + +/** + * Supported CEP id strings. + * @returns {any} + */ +export function supportedCeps() { + const ret = wasm.supportedCeps(); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_bb96b2010945f0bc: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_parse_1cc93481b0865939: function() { return handleError(function (arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); + return ret; + }, arguments); }, + __wbindgen_cast_0000000000000001: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./ceps_client_wasm_bg.js": import0, + }; +} + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; +} + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; +} + +let WASM_VECTOR_LEN = 0; + +let wasmModule, wasmInstance, wasm; +function __wbg_finalize_init(instance, module) { + wasmInstance = instance; + wasm = instance.exports; + wasmModule = module; + cachedUint8ArrayMemory0 = null; + wasm.__wbindgen_start(); + return wasm; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (!module.ok) { + throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`); + } + + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (module_or_path === undefined) { + module_or_path = new URL('ceps_client_wasm_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync, __wbg_init as default }; diff --git a/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm b/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm new file mode 100644 index 0000000..54c5e08 Binary files /dev/null and b/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm differ diff --git a/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm.d.ts b/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm.d.ts new file mode 100644 index 0000000..4c995f3 --- /dev/null +++ b/ceps-client-wasm-schema/pkg/ceps_client_wasm_bg.wasm.d.ts @@ -0,0 +1,17 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const cep18SchemaJson: () => [number, number]; +export const cep78SchemaJson: () => [number, number]; +export const cep85SchemaJson: () => [number, number]; +export const cep95SchemaJson: () => [number, number]; +export const schemaJson: (a: number, b: number) => [number, number, number, number]; +export const supportedCeps: () => [number, number, number]; +export const __wbindgen_exn_store: (a: number) => void; +export const __externref_table_alloc: () => number; +export const __wbindgen_externrefs: WebAssembly.Table; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __externref_table_dealloc: (a: number) => void; +export const __wbindgen_start: () => void; diff --git a/ceps-client-wasm-schema/pkg/package.json b/ceps-client-wasm-schema/pkg/package.json new file mode 100644 index 0000000..2ae9d72 --- /dev/null +++ b/ceps-client-wasm-schema/pkg/package.json @@ -0,0 +1,25 @@ +{ + "name": "ceps-client-wasm-schema", + "type": "module", + "collaborators": [ + "3099551+gRoussac@users.noreply.github.com" + ], + "description": "wasm-bindgen CEP client for Node and browsers (same API as ceps-client; replaces per-CEP client-js).", + "version": "1.0.0", + "license": "SEE LICENSE IN ../LICENSE", + "repository": { + "type": "git", + "url": "https://github.com/Interchouette-ITC/ceps-rust-ts-client" + }, + "files": [ + "ceps_client_wasm_bg.wasm", + "ceps_client_wasm.js", + "ceps_client_wasm.d.ts" + ], + "main": "ceps_client_wasm.js", + "homepage": "https://github.com/Interchouette-ITC/ceps-rust-ts-client", + "types": "ceps_client_wasm.d.ts", + "sideEffects": [ + "./snippets/*" + ] +} diff --git a/docker/Dockerfile b/docker/Dockerfile index fc33ecd..a2449f5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,6 +4,7 @@ FROM --platform=linux/amd64 node:24-alpine AS build WORKDIR /app COPY casper-rust-wasm-sdk ./casper-rust-wasm-sdk +COPY ceps-client-wasm-schema ./ceps-client-wasm-schema COPY wasm ./wasm COPY www/package.json www/package-lock.json www/decorate-angular-cli.js ./www/ WORKDIR /app/www diff --git a/www/apps/frontend/project.json b/www/apps/frontend/project.json index 4e08b40..fe877d4 100644 --- a/www/apps/frontend/project.json +++ b/www/apps/frontend/project.json @@ -31,6 +31,11 @@ "input": "node_modules/casper-rust-wasm-sdk", "glob": "casper_rust_wasm_sdk_bg.wasm", "output": "assets" + }, + { + "input": "node_modules/ceps-client-wasm-schema", + "glob": "ceps_client_wasm_bg.wasm", + "output": "assets" } ], "styles": [ diff --git a/www/libs/api-interfaces/src/index.ts b/www/libs/api-interfaces/src/index.ts index db9b100..2ebd767 100644 --- a/www/libs/api-interfaces/src/index.ts +++ b/www/libs/api-interfaces/src/index.ts @@ -2,3 +2,4 @@ export * from './lib/api-interfaces'; export * from './lib/api-enums'; export * from './lib/cl-type'; export * from './lib/stored-value'; +export * from './lib/session-args'; diff --git a/www/libs/api-interfaces/src/lib/api-interfaces.ts b/www/libs/api-interfaces/src/lib/api-interfaces.ts index 64915d5..dcae1a0 100644 --- a/www/libs/api-interfaces/src/lib/api-interfaces.ts +++ b/www/libs/api-interfaces/src/lib/api-interfaces.ts @@ -3,6 +3,10 @@ import { CLType } from './cl-type'; export type NamedCLTypeArg = { name: string; cl_type: CLType; + /** Prefer this when emitting session_args_json (from chain or ceps schema). */ + session_type?: string | Record; + /** Prefill value from existing Args JSON. */ + value?: unknown; entry_points?: string[]; install?: boolean; entry_point?: boolean; diff --git a/www/libs/api-interfaces/src/lib/session-args.ts b/www/libs/api-interfaces/src/lib/session-args.ts new file mode 100644 index 0000000..28c987f --- /dev/null +++ b/www/libs/api-interfaces/src/lib/session-args.ts @@ -0,0 +1,439 @@ +/** Session args JSON helpers for the Args builder (casper-client session_args_json shape). */ + +import { CLType } from './cl-type'; +import { NamedCLTypeArg } from './api-interfaces'; + +/** One entry in session_args_json / ceps ArgField type field. */ +export type SessionArgType = string | Record; + +/** Parsed runtime arg row. */ +export type SessionArgRow = { + name: string; + type: SessionArgType; + value?: unknown; +}; + +/** ceps-client schema ArgField. */ +export type CepArgField = { + name: string; + type: SessionArgType; + optional?: boolean; +}; + +/** Full CEP schema from schemaJson(). */ +export type CepSchema = { + cep: string; + install: CepArgField[]; + entrypoints: Record; +}; + +/** Parse session_args_json; empty / invalid → []. */ +export function parseSessionArgsJson(json: string | null | undefined): SessionArgRow[] { + if (!json || !String(json).trim()) { + return []; + } + try { + const parsed = JSON.parse(String(json).trim()) as unknown; + if (!Array.isArray(parsed)) { + return []; + } + const rows: SessionArgRow[] = []; + for (const item of parsed) { + if (!item || typeof item !== 'object') { + continue; + } + const name = (item as { name?: unknown }).name; + const type = (item as { type?: unknown }).type; + if (typeof name !== 'string' || name.length === 0 || type === undefined) { + continue; + } + rows.push({ + name, + type: type as SessionArgType, + value: (item as { value?: unknown }).value, + }); + } + return rows; + } catch { + return []; + } +} + +/** Serialize rows to session_args_json. Omits rows with empty name or undefined value. */ +export function serializeSessionArgsJson(rows: SessionArgRow[]): string { + const out = rows + .filter((r) => r.name && r.value !== undefined && r.value !== '') + .map((r) => ({ + name: r.name, + type: r.type, + value: r.value, + })); + return JSON.stringify(out); +} + +/** RPC / Parameter cl_type → session_args_json type field. */ +export function clTypeToSessionType(clType: unknown): SessionArgType { + if (clType === null || clType === undefined) { + return 'Any'; + } + if (typeof clType === 'string') { + return clType; + } + if (typeof clType === 'object') { + const obj = clType as Record; + if ('Option' in obj) { + return { Option: clTypeToSessionType(obj['Option']) }; + } + if ('List' in obj) { + return { List: clTypeToSessionType(obj['List']) }; + } + if ('ByteArray' in obj) { + return { ByteArray: obj['ByteArray'] as number }; + } + if ('Result' in obj) { + const result = obj['Result'] as { ok?: unknown; err?: unknown } | unknown[]; + if (Array.isArray(result) && result.length >= 2) { + return { + Result: { + ok: clTypeToSessionType(result[0]), + err: clTypeToSessionType(result[1]), + }, + }; + } + const r = result as { ok?: unknown; err?: unknown }; + return { + Result: { + ok: clTypeToSessionType(r.ok), + err: clTypeToSessionType(r.err), + }, + }; + } + if ('Map' in obj) { + const map = obj['Map'] as { key?: unknown; value?: unknown } | unknown[]; + if (Array.isArray(map) && map.length >= 2) { + return { + Map: { + key: clTypeToSessionType(map[0]), + value: clTypeToSessionType(map[1]), + }, + }; + } + const m = map as { key?: unknown; value?: unknown }; + return { + Map: { + key: clTypeToSessionType(m.key), + value: clTypeToSessionType(m.value), + }, + }; + } + if ('Tuple1' in obj) { + const t = obj['Tuple1']; + const inner = Array.isArray(t) ? t[0] : t; + return { Tuple1: [clTypeToSessionType(inner)] }; + } + if ('Tuple2' in obj) { + const t = obj['Tuple2'] as unknown[]; + return { + Tuple2: [clTypeToSessionType(t?.[0]), clTypeToSessionType(t?.[1])], + }; + } + if ('Tuple3' in obj) { + const t = obj['Tuple3'] as unknown[]; + return { + Tuple3: [ + clTypeToSessionType(t?.[0]), + clTypeToSessionType(t?.[1]), + clTypeToSessionType(t?.[2]), + ], + }; + } + } + if (clType instanceof CLType || (clType as { toString?: () => string }).toString) { + return sessionTypeFromClTypeLabel(String(clType)); + } + return 'Any'; +} + +/** Best-effort parse of CLType.toString() labels into session type. */ +function sessionTypeFromClTypeLabel(label: string): SessionArgType { + const trimmed = label.trim(); + const option = /^Option\((.*)\)$/.exec(trimmed); + if (option) { + return { Option: sessionTypeFromClTypeLabel(option[1]) }; + } + const list = /^List\((.*)\)$/.exec(trimmed); + if (list) { + return { List: sessionTypeFromClTypeLabel(list[1]) }; + } + const map = /^Map\((.*), (.*)\)$/.exec(trimmed); + if (map) { + return { + Map: { + key: sessionTypeFromClTypeLabel(map[1]), + value: sessionTypeFromClTypeLabel(map[2]), + }, + }; + } + const tuple1 = /^Tuple1\((.*)\)$/.exec(trimmed); + if (tuple1) { + return { Tuple1: [sessionTypeFromClTypeLabel(tuple1[1])] }; + } + const tuple2 = /^Tuple2\((.*), (.*)\)$/.exec(trimmed); + if (tuple2) { + return { + Tuple2: [ + sessionTypeFromClTypeLabel(tuple2[1]), + sessionTypeFromClTypeLabel(tuple2[2]), + ], + }; + } + const tuple3 = /^Tuple3\((.*), (.*), (.*)\)$/.exec(trimmed); + if (tuple3) { + return { + Tuple3: [ + sessionTypeFromClTypeLabel(tuple3[1]), + sessionTypeFromClTypeLabel(tuple3[2]), + sessionTypeFromClTypeLabel(tuple3[3]), + ], + }; + } + const result = /^Result\((.*), (.*)\)$/.exec(trimmed); + if (result) { + return { + Result: { + ok: sessionTypeFromClTypeLabel(result[1]), + err: sessionTypeFromClTypeLabel(result[2]), + }, + }; + } + return trimmed || 'Any'; +} + +/** Build a CLType for the argument select from a session type. */ +export function sessionTypeToClType(type: SessionArgType): CLType { + if (typeof type === 'string') { + switch (type) { + case 'Bool': + return CLType.Bool(); + case 'I32': + return CLType.I32(); + case 'I64': + return CLType.I64(); + case 'U8': + return CLType.U8(); + case 'U32': + return CLType.U32(); + case 'U64': + return CLType.U64(); + case 'U128': + return CLType.U128(); + case 'U256': + return CLType.U256(); + case 'U512': + return CLType.U512(); + case 'Unit': + return CLType.Unit(); + case 'String': + return CLType.String(); + case 'Key': + return CLType.Key(); + case 'URef': + return CLType.URef(); + case 'PublicKey': + return CLType.PublicKey(); + case 'ByteArray': + return CLType.ByteArray(); + case 'Any': + return CLType.Any(); + default: + return CLType.Any(); + } + } + if (type && typeof type === 'object') { + if ('Option' in type) { + return CLType.Option(sessionTypeToClType(type['Option'] as SessionArgType)); + } + if ('List' in type) { + return CLType.List(sessionTypeToClType(type['List'] as SessionArgType)); + } + if ('ByteArray' in type) { + return CLType.ByteArray(); + } + if ('Map' in type) { + const map = type['Map'] as { key: SessionArgType; value: SessionArgType }; + return CLType.Map( + sessionTypeToClType(map.key), + sessionTypeToClType(map.value), + ); + } + if ('Result' in type) { + const result = type['Result'] as { + ok: SessionArgType; + err: SessionArgType; + }; + return CLType.Result( + sessionTypeToClType(result.ok), + sessionTypeToClType(result.err), + ); + } + if ('Tuple1' in type) { + const t = type['Tuple1'] as SessionArgType[]; + return CLType.Tuple1(sessionTypeToClType(t[0])); + } + if ('Tuple2' in type) { + const t = type['Tuple2'] as SessionArgType[]; + return CLType.Tuple2( + sessionTypeToClType(t[0]), + sessionTypeToClType(t[1]), + ); + } + if ('Tuple3' in type) { + const t = type['Tuple3'] as SessionArgType[]; + return CLType.Tuple3( + sessionTypeToClType(t[0]), + sessionTypeToClType(t[1]), + sessionTypeToClType(t[2]), + ); + } + } + return CLType.Any(); +} + +/** On-chain Parameter-like → NamedCLTypeArg for storage / Custom tab. */ +export function parameterToNamedArg(param: { + name?: string; + cl_type?: unknown; +}): NamedCLTypeArg | null { + if (!param?.name || typeof param.name !== 'string') { + return null; + } + const sessionType = clTypeToSessionType(param.cl_type); + return { + name: param.name, + cl_type: sessionTypeToClType(sessionType), + session_type: sessionType, + }; +} + +/** ceps ArgField → NamedCLTypeArg. */ +export function cepFieldToNamedArg(field: CepArgField): NamedCLTypeArg { + return { + name: field.name, + cl_type: sessionTypeToClType(field.type), + session_type: field.type, + optional: !!field.optional, + }; +} + +/** Normalize any schema-like row into NamedCLTypeArg. */ +export function toNamedArg(raw: unknown): NamedCLTypeArg | null { + if (!raw || typeof raw !== 'object') { + return null; + } + const obj = raw as Record; + if (typeof obj['name'] !== 'string' || !obj['name']) { + return null; + } + if ('type' in obj && !('cl_type' in obj)) { + return cepFieldToNamedArg(obj as unknown as CepArgField); + } + if ('cl_type' in obj) { + const cl = obj['cl_type']; + if (cl instanceof CLType) { + return { + name: obj['name'] as string, + cl_type: cl, + session_type: + (obj['session_type'] as SessionArgType | undefined) ?? + clTypeToSessionType(String(cl)), + optional: !!obj['optional'], + value: obj['value'], + }; + } + const sessionType = + (obj['session_type'] as SessionArgType | undefined) ?? + clTypeToSessionType(cl); + return { + name: obj['name'] as string, + cl_type: sessionTypeToClType(sessionType), + session_type: sessionType, + optional: !!obj['optional'], + value: obj['value'], + }; + } + return null; +} + +/** + * Merge schema (names/types) with existing JSON values by name. + * Schema types win; values come from the JSON rows when present. + */ +export function mergeSchemaWithValues( + schema: unknown[], + values: SessionArgRow[], +): NamedCLTypeArg[] { + const byName = new Map(values.map((v) => [v.name, v])); + const rows: NamedCLTypeArg[] = []; + for (const raw of schema) { + const row = toNamedArg(raw); + if (!row) { + continue; + } + const existing = byName.get(row.name); + if (existing && existing.value !== undefined) { + rows.push({ ...row, value: existing.value }); + } else { + rows.push(row); + } + } + return rows; +} + +/** Coerce a form value for session_args_json based on session type. */ +export function coerceSessionValue( + type: SessionArgType, + raw: string, +): unknown { + const trimmed = raw.trim(); + if (trimmed === '') { + return undefined; + } + if (typeof type === 'string') { + switch (type) { + case 'Bool': + return trimmed.toLowerCase() === 'true'; + case 'I32': + case 'I64': + case 'U8': + case 'U32': + case 'U64': { + const n = Number(trimmed); + return Number.isFinite(n) ? n : trimmed; + } + case 'U128': + case 'U256': + case 'U512': + return trimmed; + case 'Unit': + return null; + default: + return trimmed; + } + } + try { + return JSON.parse(trimmed); + } catch { + return trimmed; + } +} + +/** NamedCLTypeArg list → SessionArgRow list for serialize. */ +export function namedArgsToSessionRows(args: NamedCLTypeArg[]): SessionArgRow[] { + return args + .filter((a) => a.name && a.value !== undefined && a.value !== '') + .map((a) => ({ + name: a.name, + type: a.session_type ?? clTypeToSessionType(a.cl_type), + value: a.value, + })); +} diff --git a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.html b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.html index 7f984b3..710df43 100644 --- a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.html +++ b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.html @@ -42,22 +42,12 @@

Args builder

[active]="active" >
-
- @for (tab of defaultTabs; track tab) { - @if (tab['name'] === active) { - @for (type of tab.types; track trackByFn(i, type); let i = $index) { - @if ( - (!hasWasm && - (!(type.install || type.upgrade) || - type.entry_point)) || - (hasWasm && (type.install || type.upgrade)) - ) { - - } - } - } +
+ @for (row of rows; track trackByFn(i, row); let i = $index) { + }
diff --git a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.spec.ts b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.spec.ts index 458e209..ae0018f 100644 --- a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.spec.ts +++ b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.spec.ts @@ -3,22 +3,66 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArgBuilderComponent } from './arg-builder.component'; import { DeployerService } from '@casper-data/data-access-deployer'; import { StorageService } from '@casper-util/storage'; +import { CepSchemaService, seedCepSchemaCache } from './cep-schema.service'; +import { of } from 'rxjs'; +import { Tabs } from '@casper-ui/tabs'; describe('ArgBuilderComponent', () => { let component: ArgBuilderComponent; let fixture: ComponentFixture; - - const getState = jest.fn().mockReturnValue({ subscribe: jest.fn() }); + let storage: { get: jest.Mock; setState: jest.Mock }; beforeEach(async () => { + storage = { + get: jest.fn((key: string) => { + if (key === 'deploy_args') { + return JSON.stringify([ + { name: 'name', type: 'String', value: 'TOKEN' }, + { name: 'symbol', type: 'String', value: 'TKN' }, + ]); + } + if (key === 'args') { + return []; + } + if (key === 'entry_point') { + return ''; + } + return undefined; + }), + setState: jest.fn(), + }; + + const cepSchema = new CepSchemaService(); + seedCepSchemaCache(cepSchema, [ + { + cep: 'cep18', + install: [ + { name: 'name', type: 'String', optional: false }, + { name: 'symbol', type: 'String', optional: false }, + { name: 'decimals', type: 'U8', optional: false }, + { name: 'total_supply', type: 'U256', optional: false }, + ], + entrypoints: { + transfer: [ + { name: 'recipient', type: 'Key', optional: false }, + { name: 'amount', type: 'U256', optional: false }, + ], + }, + }, + ]); + await TestBed.configureTestingModule({ imports: [ArgBuilderComponent], providers: [ - { provide: DeployerService, useValue: { getState } }, { - provide: StorageService, - useValue: { get: jest.fn(), set: jest.fn(), setState: jest.fn() }, + provide: DeployerService, + useValue: { + getState: () => of({ has_wasm: true }), + setState: jest.fn(), + }, }, + { provide: StorageService, useValue: storage }, + { provide: CepSchemaService, useValue: cepSchema }, ], }).compileComponents(); @@ -30,4 +74,31 @@ describe('ArgBuilderComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('prefills install rows from deploy_args when wasm and CEP tab', async () => { + component.hasWasm = true; + component.active = Tabs['CEP-18']; + component.isOpen = true; + await Promise.resolve(); + await Promise.resolve(); + expect(component.rows.length).toBeGreaterThanOrEqual(2); + const nameRow = component.rows.find((r) => r.name === 'name'); + expect(nameRow?.value).toBe('TOKEN'); + }); + + it('build emits session_args_json and stores deploy_args', async () => { + component.hasWasm = true; + component.active = Tabs['CEP-18']; + component.isOpen = true; + await Promise.resolve(); + await Promise.resolve(); + const emitted: string[] = []; + component.argumentChanged.subscribe((v) => emitted.push(v)); + component.rows = component.rows.map((r) => + r.name === 'decimals' ? { ...r, value: '9' } : r, + ); + component.build(); + expect(emitted[0]).toContain('"name":"name"'); + expect(storage.setState).toHaveBeenCalled(); + }); }); diff --git a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.ts b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.ts index 8fce674..4f3d973 100644 --- a/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.ts +++ b/www/libs/feature/deployer/src/lib/arg-builder/arg-builder.component.ts @@ -1,50 +1,30 @@ import { - AfterViewInit, ChangeDetectionStrategy, + ChangeDetectorRef, Component, - ElementRef, EventEmitter, Input, OnDestroy, Output, - ViewChild, } from '@angular/core'; import { Tabs, TabsComponent } from '@casper-ui/tabs'; import { ArgumentComponent } from '@casper-ui/argument'; -import { customArg, defaultTabs } from './tabs'; +import { defaultTabs } from './tabs'; import { StorageService } from '@casper-util/storage'; -import { NamedCLTypeArg, State, CLType } from '@casper-api/api-interfaces'; +import { + NamedCLTypeArg, + State, + CLType, + parseSessionArgsJson, + mergeSchemaWithValues, + serializeSessionArgsJson, + coerceSessionValue, + SessionArgType, +} from '@casper-api/api-interfaces'; import { DeployerService } from '@casper-data/data-access-deployer'; import { Subscription } from 'rxjs'; - -interface ArgumentEntry { - name: string; - type: string | TypeObject; - value: T; -} - -interface TypeObject { - Option?: string; - List?: string | TypeObject; - Tuple1?: string[]; - Tuple2?: string[]; - Tuple3?: string[]; - Map?: { key: string; value: string }; - ByteArray?: number; - Result?: { ok: string; err: string }; -} - -const sortByName = (a: NamedCLTypeArg, b: NamedCLTypeArg) => { - const typeA = a['name'].toString().toUpperCase(); - const typeB = b['name'].toString().toUpperCase(); - if (typeA < typeB) { - return -1; - } else if (typeA > typeB) { - return 1; - } - return 0; -}; +import { CepSchemaService, tabToCepId } from './cep-schema.service'; @Component({ selector: 'casper-deployer-arg-builder', @@ -54,10 +34,12 @@ const sortByName = (a: NamedCLTypeArg, b: NamedCLTypeArg) => { styleUrls: ['./arg-builder.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ArgBuilderComponent implements AfterViewInit, OnDestroy { +export class ArgBuilderComponent implements OnDestroy { @Input() set isOpen(value: boolean) { this._isOpen = value; - this.addArgs(); + if (value) { + void this.hydrate(); + } } get isOpen(): boolean { @@ -66,185 +48,148 @@ export class ArgBuilderComponent implements AfterViewInit, OnDestroy { @Output() closeModal: EventEmitter = new EventEmitter(); @Output() argumentChanged: EventEmitter = new EventEmitter(); - @ViewChild('form') formElt!: ElementRef; + Tabs = Tabs; default = Tabs.Custom; active: Tabs = this.default; - defaultTabs = defaultTabs.map((tab) => ({ - name: tab.name, - types: tab.types.slice().sort(sortByName), - })); - argument = ''; - hasWasm!: boolean; + tabDefs = defaultTabs; + /** Rows shown for the active tab. */ + rows: NamedCLTypeArg[] = []; + hasWasm = false; + entryPoint = ''; private _isOpen = false; - private getStateSubscription!: Subscription; + private getStateSubscription: Subscription; constructor( private readonly storageService: StorageService, private readonly deployerService: DeployerService, - ) {} - - ngAfterViewInit(): void { + private readonly cepSchemaService: CepSchemaService, + private readonly changeDetectorRef: ChangeDetectorRef, + ) { this.getStateSubscription = this.deployerService .getState() .subscribe((state: State) => { if (undefined !== state.has_wasm) { this.hasWasm = !!state.has_wasm; } + if (state.entry_point !== undefined) { + this.entryPoint = state.entry_point || ''; + } + if (this._isOpen) { + void this.hydrate(); + } }); } ngOnDestroy(): void { - this.getStateSubscription && this.getStateSubscription.unsubscribe(); + this.getStateSubscription?.unsubscribe(); } activateContent(tabIndex: Tabs) { this.active = tabIndex; + void this.hydrate(); } add() { - this.defaultTabs[this.active].types.push(customArg); + this.rows = [ + ...this.rows, + { name: '', cl_type: CLType.U8(), session_type: 'U8', value: '' }, + ]; + this.changeDetectorRef.markForCheck(); } trackByFn = (index: number, item: NamedCLTypeArg): string => - item['name'].toString(); + `${item.name}-${index}`; - build() { - this.argument = '['; - const collection = this.formElt.nativeElement.children as HTMLCollection; - - Array.from(collection).forEach((collection: Element) => { - const children = Array.from(collection.children); - - if (!(children[2] as HTMLInputElement).value) { - return; - } - const entry: ArgumentEntry = { - name: this.parseName( - children[2] as HTMLInputElement, - (children[1] as HTMLInputElement).value, - (children[0] as HTMLInputElement).value, - ), - type: this.parseType( - children[2] as HTMLInputElement, - (children[1] as HTMLInputElement).value, - ), - value: this.parseValue( - children[2] as HTMLInputElement, - (children[1] as HTMLInputElement).value, - ), - }; - - this.argument += JSON.stringify(entry) + ','; - }); - - this.argument = this.argument.slice(0, -1); // Remove trailing comma - this.argument += ']'; - - this.argument && this.argumentChanged.emit(this.argument); + onRowChange(index: number, row: NamedCLTypeArg) { + const next = [...this.rows]; + next[index] = row; + this.rows = next; } - private parseName( - input: HTMLInputElement, - type: string, - name: string, - ): string { - const inputValue = input.value.trim(); - switch (type) { - case CLType.Option(CLType.Any()).toString(): - case CLType.List(CLType.Any()).toString(): - case CLType.ByteArray().toString(): - case CLType.Result(CLType.Any(), CLType.Any()).toString(): - case CLType.Map(CLType.Any(), CLType.Any()).toString(): - case CLType.Tuple1(CLType.Any()).toString(): - case CLType.Tuple2(CLType.Any(), CLType.Any()).toString(): - case CLType.Tuple3(CLType.Any(), CLType.Any(), CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return parsedInput.name || name; - } - default: - return name; + build() { + const sessionRows = this.rows + .map((row) => { + const type: SessionArgType = + row.session_type ?? String(row.cl_type ?? 'Any'); + const raw = + row.value === undefined || row.value === null + ? '' + : typeof row.value === 'string' + ? row.value + : JSON.stringify(row.value); + const value = coerceSessionValue(type, raw); + return { + name: row.name?.replace(/\s*\*$/, '').trim() || '', + type, + value, + }; + }) + .filter((r) => r.name && r.value !== undefined); + + const json = serializeSessionArgsJson(sessionRows); + if (json && json !== '[]') { + this.storageService.setState({ deploy_args: json }); + this.argumentChanged.emit(json); } } - private parseType(input: HTMLInputElement, type: string) { - const inputValue = input.value.trim(); - switch (type) { - case CLType.Option(CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Option: parsedInput.type.Option }; - } - case CLType.List(CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { List: parsedInput.type.List }; - } - case CLType.ByteArray().toString(): { - const parsedInput = JSON.parse(inputValue); - return { ByteArray: parsedInput.type.ByteArray }; - } - case CLType.Result(CLType.Any(), CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Result: parsedInput.type.Result }; - } - case CLType.Map(CLType.Any(), CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Result: parsedInput.type.Map }; - } - case CLType.Tuple1(CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Tuple1: parsedInput.type.Tuple1 }; - } - case CLType.Tuple2(CLType.Any(), CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Tuple2: parsedInput.type.Tuple2 }; - } - case CLType.Tuple3(CLType.Any(), CLType.Any(), CLType.Any()).toString(): { - const parsedInput = JSON.parse(inputValue); - return { Tuple3: parsedInput.type.Tuple3 }; + private async hydrate(): Promise { + await this.cepSchemaService.ensureReady().catch(() => undefined); + + const deployArgs = parseSessionArgsJson( + this.storageService.get('deploy_args') || '', + ); + const storedArgs = (this.storageService.get('args') || []) as unknown[]; + const entryPoint = + this.entryPoint || this.storageService.get('entry_point') || ''; + this.entryPoint = entryPoint; + this.hasWasm = !!( + this.hasWasm || this.storageService.get('has_wasm') + ); + + const tab = this.tabDefs.find((t) => t.name === this.active); + const cepId = tab?.cepId ?? tabToCepId(Tabs[this.active] || ''); + + let schemaRows: NamedCLTypeArg[] = []; + + if (Array.isArray(storedArgs) && storedArgs.length > 0) { + // On-chain entrypoint schema wins on Custom (and when present). + if (this.active === Tabs.Custom || !cepId) { + schemaRows = mergeSchemaWithValues(storedArgs, deployArgs); + if (this.active !== Tabs.Custom && schemaRows.length) { + this.active = Tabs.Custom; + } } - default: - return type; } - } - private parseValue(input: HTMLInputElement, type: string): T { - const inputValue = input.value.trim(); - - switch (type) { - case CLType.Bool().toString(): - return (inputValue.toLowerCase() === 'true') as unknown as T; - case CLType.I32().toString(): - case CLType.I64().toString(): - case CLType.U8().toString(): - case CLType.U32().toString(): - case CLType.U64().toString(): - case CLType.U128().toString(): - case CLType.U256().toString(): - case CLType.U512().toString(): - return Number(inputValue) as unknown as T; - case CLType.Unit().toString(): - return null as unknown as T; - case CLType.String().toString(): - return inputValue as unknown as T; - case CLType.Key().toString(): - case CLType.URef().toString(): - case CLType.PublicKey().toString(): - case CLType.Any().toString(): - return inputValue as unknown as T; - default: { - const test = JSON.parse(inputValue); - return test.value as unknown as T; + if (!schemaRows.length && cepId) { + if (this.hasWasm && !entryPoint) { + schemaRows = mergeSchemaWithValues( + this.cepSchemaService.installArgs(cepId), + deployArgs, + ); + } else if (entryPoint) { + schemaRows = mergeSchemaWithValues( + this.cepSchemaService.entrypointArgs(cepId, entryPoint), + deployArgs, + ); } } - } - private addArgs() { - const args: [] = this.storageService.get('args'); - this.defaultTabs[0].types = []; - args && - args.forEach((arg) => { - this.defaultTabs[0].types.push(arg); - }); + if (!schemaRows.length && deployArgs.length) { + schemaRows = mergeSchemaWithValues( + deployArgs.map((r) => ({ + name: r.name, + type: r.type, + optional: false, + })), + deployArgs, + ); + } + + this.rows = schemaRows; + this.changeDetectorRef.markForCheck(); } } diff --git a/www/libs/feature/deployer/src/lib/arg-builder/cep-schema.service.ts b/www/libs/feature/deployer/src/lib/arg-builder/cep-schema.service.ts new file mode 100644 index 0000000..f28380e --- /dev/null +++ b/www/libs/feature/deployer/src/lib/arg-builder/cep-schema.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@angular/core'; +import { + CepArgField, + CepSchema, + cepFieldToNamedArg, + NamedCLTypeArg, +} from '@casper-api/api-interfaces'; +import init, { schemaJson, supportedCeps } from 'ceps-client-wasm-schema'; + +const CEP_IDS = ['cep18', 'cep78', 'cep85', 'cep95'] as const; +export type CepId = (typeof CEP_IDS)[number]; + +/** Loads ceps schema-only wasm and caches CEP-18/78/85/95 arg schemas. */ +@Injectable({ providedIn: 'root' }) +export class CepSchemaService { + private readonly cache = new Map(); + private initPromise: Promise | null = null; + private ready = false; + + /** Ensure wasm is initialized and schemas cached. */ + async ensureReady(): Promise { + if (this.ready) { + return; + } + if (!this.initPromise) { + this.initPromise = this.load(); + } + await this.initPromise; + } + + /** Synchronous get after ensureReady (or empty if not loaded). */ + getSchema(cep: string): CepSchema | undefined { + return this.cache.get(normalizeCepId(cep)); + } + + installArgs(cep: string): NamedCLTypeArg[] { + const schema = this.getSchema(cep); + return (schema?.install ?? []).map((f) => cepFieldToNamedArg(f)); + } + + entrypointArgs(cep: string, entryPoint: string): NamedCLTypeArg[] { + const schema = this.getSchema(cep); + const fields: CepArgField[] = schema?.entrypoints?.[entryPoint] ?? []; + return fields.map((f) => cepFieldToNamedArg(f)); + } + + supported(): string[] { + return [...this.cache.keys()]; + } + + private async load(): Promise { + await init({ + module_or_path: 'assets/ceps_client_wasm_bg.wasm', + }); + const ids = (supportedCeps() as string[]) || [...CEP_IDS]; + for (const id of ids) { + const raw = schemaJson(id); + const parsed = JSON.parse(raw) as CepSchema; + this.cache.set(normalizeCepId(id), parsed); + } + this.ready = true; + } +} + +/** Map UI tab label to ceps id. */ +export function tabToCepId(tabLabel: string): CepId | null { + const key = tabLabel.replace('-', '').toLowerCase(); + if (key === 'cep18' || key === '18') { + return 'cep18'; + } + if (key === 'cep78' || key === '78') { + return 'cep78'; + } + if (key === 'cep85' || key === '85') { + return 'cep85'; + } + if (key === 'cep95' || key === '95') { + return 'cep95'; + } + return null; +} + +function normalizeCepId(cep: string): string { + return cep.trim().toLowerCase(); +} + +/** Seed cache from pre-parsed schemas (unit tests). */ +export function seedCepSchemaCache( + service: CepSchemaService, + schemas: CepSchema[], +): void { + const anyService = service as unknown as { + cache: Map; + ready: boolean; + }; + for (const schema of schemas) { + anyService.cache.set(normalizeCepId(schema.cep), schema); + } + anyService.ready = true; +} diff --git a/www/libs/feature/deployer/src/lib/arg-builder/session-args.spec.ts b/www/libs/feature/deployer/src/lib/arg-builder/session-args.spec.ts new file mode 100644 index 0000000..80fe097 --- /dev/null +++ b/www/libs/feature/deployer/src/lib/arg-builder/session-args.spec.ts @@ -0,0 +1,75 @@ +import { + parseSessionArgsJson, + serializeSessionArgsJson, + clTypeToSessionType, + mergeSchemaWithValues, + sessionTypeToClType, + parameterToNamedArg, + coerceSessionValue, +} from '@casper-api/api-interfaces'; +import { CLType } from '@casper-api/api-interfaces'; + +describe('session-args helpers', () => { + it('parses and serializes session_args_json', () => { + const json = + '[{"name":"amount","type":"U256","value":"1000"},{"name":"owner","type":"Key","value":"account-hash-ab"}]'; + const rows = parseSessionArgsJson(json); + expect(rows).toHaveLength(2); + expect(rows[0].name).toBe('amount'); + expect(rows[0].type).toBe('U256'); + expect(serializeSessionArgsJson(rows)).toContain('U256'); + }); + + it('returns [] for invalid json', () => { + expect(parseSessionArgsJson('')).toEqual([]); + expect(parseSessionArgsJson('not-json')).toEqual([]); + expect(parseSessionArgsJson('{}')).toEqual([]); + }); + + it('maps nested List CLTypes', () => { + expect(clTypeToSessionType({ List: 'Key' })).toEqual({ List: 'Key' }); + expect(clTypeToSessionType({ List: { Tuple2: ['String', 'String'] } })).toEqual({ + List: { Tuple2: ['String', 'String'] }, + }); + const cl = sessionTypeToClType({ List: 'U256' }); + expect(cl.toString()).toContain('List'); + }); + + it('merges schema with values by name', () => { + const merged = mergeSchemaWithValues( + [ + { name: 'name', type: 'String', optional: false }, + { name: 'decimals', type: 'U8', optional: false }, + ], + [{ name: 'name', type: 'String', value: 'TOKEN' }], + ); + expect(merged[0].value).toBe('TOKEN'); + expect(merged[0].session_type).toBe('String'); + expect(merged[1].value).toBeUndefined(); + expect(merged[1].cl_type.toString()).toBe(CLType.U8().toString()); + }); + + it('normalizes on-chain parameters', () => { + const arg = parameterToNamedArg({ + name: 'recipient', + cl_type: 'Key', + }); + expect(arg?.name).toBe('recipient'); + expect(arg?.session_type).toBe('Key'); + }); + + it('keeps U256 as string', () => { + expect(coerceSessionValue('U256', '1000000000000000000')).toBe( + '1000000000000000000', + ); + expect(coerceSessionValue('U8', '9')).toBe(9); + expect(coerceSessionValue('Bool', 'true')).toBe(true); + }); + + it('parses Map nested type objects', () => { + const type = clTypeToSessionType({ + Map: { key: 'String', value: 'String' }, + }); + expect(type).toEqual({ Map: { key: 'String', value: 'String' } }); + }); +}); diff --git a/www/libs/feature/deployer/src/lib/arg-builder/tabs.ts b/www/libs/feature/deployer/src/lib/arg-builder/tabs.ts index 1eeda7e..d5737ed 100644 --- a/www/libs/feature/deployer/src/lib/arg-builder/tabs.ts +++ b/www/libs/feature/deployer/src/lib/arg-builder/tabs.ts @@ -1,311 +1,11 @@ -import { NamedCLTypeArg, CLType } from '@casper-api/api-interfaces'; import { Tabs } from '@casper-ui/tabs'; - -const customArg: NamedCLTypeArg = { name: '', cl_type: CLType.U8() }; - -const nameArg: NamedCLTypeArg = { name: 'name', cl_type: CLType?.String() }; - -const symbolArg: NamedCLTypeArg = { name: 'symbol', cl_type: CLType?.String() }; - -const decimalsArg: NamedCLTypeArg = { name: 'decimals', cl_type: CLType.U8() }; - -const ownerArg: NamedCLTypeArg = { name: 'owner', cl_type: CLType?.Key() }; - -const spenderArg: NamedCLTypeArg = { name: 'spender', cl_type: CLType?.Key() }; - -const recipientArg: NamedCLTypeArg = { - name: 'recipient', - cl_type: CLType?.Key(), -}; - -export const defaultTabs: { name: Tabs; types: NamedCLTypeArg[] }[] = [ - { name: Tabs.Custom, types: [] }, - { - name: Tabs['CEP-18'], - types: [ - { ...nameArg, install: true }, - { ...symbolArg, install: true }, - { ...decimalsArg, install: true }, - { name: 'total_supply', cl_type: CLType?.U256(), install: true }, - { - name: 'enable_mint_burn', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { - name: 'events_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { - name: 'admin_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - }, - { - name: 'minter_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - }, - { - name: 'none_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - }, - { name: 'address', cl_type: CLType?.Key() }, - { name: 'amount', cl_type: CLType?.U256() }, - ownerArg, - spenderArg, - recipientArg, - ], - }, - { - name: Tabs['CEP-47'], - types: [ - { ...nameArg, install: true }, - { ...symbolArg, install: true }, - { name: 'meta', cl_type: CLType?.String(), install: true }, - ownerArg, - { name: 'token_id', cl_type: CLType?.U256() }, - { - name: 'token_meta', - cl_type: CLType?.Map(CLType?.String(), CLType?.String()), - }, - { name: 'token_ids', cl_type: CLType?.List(CLType?.U256()) }, - { - name: 'token_metas', - cl_type: CLType?.List(CLType?.Map(CLType?.String(), CLType?.String())), - }, - recipientArg, - { name: 'count', cl_type: CLType?.U32() }, - spenderArg, - { name: 'index', cl_type: CLType?.U256() }, - ], - }, - { - name: Tabs['CEP-78'], - types: [ - { - name: 'collection_name', - cl_type: CLType?.String(), - install: true, - upgrade: true, - }, - { name: 'collection_symbol', cl_type: CLType?.String(), install: true }, - { name: 'total_token_supply', cl_type: CLType?.U64(), install: true }, - { - name: 'allow_minting', - cl_type: CLType?.Bool(), - install: true, - optional: true, - }, - { - name: 'minting_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { name: 'ownership_mode', cl_type: CLType.U8(), install: true }, - { name: 'nft_kind', cl_type: CLType.U8(), install: true }, - { name: 'nft_metadata_kind', cl_type: CLType.U8(), install: true }, - { name: 'metadata_mutability', cl_type: CLType.U8(), install: true }, - { - name: 'holder_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { - name: 'owner_reverse_lookup_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { name: 'token_id', cl_type: CLType?.U256() }, - { name: 'token_hash', cl_type: CLType?.Key() }, - { name: 'named_key_convention', cl_type: CLType.U8(), upgrade: true }, - { - name: 'whitelist_mode', - cl_type: CLType.U8(), - optional: true, - install: true, - }, - { - name: 'identifier_mode', - cl_type: CLType.U8(), - install: true, - entry_point: true, - }, - { - name: 'burn_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - }, - { - name: 'operator_burn_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - upgrade: true, - }, - { - name: 'json_schema', - cl_type: CLType?.String(), - install: true, - optional: true, - }, - { name: 'receipt_name', cl_type: CLType?.String() }, - { name: 'token_owner', cl_type: CLType?.Key() }, - { name: 'token_meta_data', cl_type: CLType?.String() }, - { name: 'source_key', cl_type: CLType?.Key() }, - { name: 'target_key', cl_type: CLType?.Key() }, - { name: 'operator', cl_type: CLType?.Key() }, - { name: 'approve_all', cl_type: CLType?.Bool() }, - { name: 'access_key_name', cl_type: CLType?.String(), upgrade: true }, - { name: 'hash_key_name', cl_type: CLType?.String(), upgrade: true }, - { - name: 'acl_white_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - }, - { - name: 'acl_package_mode', - cl_type: CLType?.Bool(), - install: true, - optional: true, - upgrade: true, - }, - { - name: 'package_operator_mode', - cl_type: CLType?.Bool(), - install: true, - optional: true, - upgrade: true, - }, - { - name: 'additional_required_metadata', - cl_type: CLType?.List(CLType?.U8()), - install: true, - optional: true, - }, - { - name: 'optional_metadata', - cl_type: CLType?.List(CLType?.U8()), - install: true, - optional: true, - }, - { - name: 'events_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - upgrade: true, - }, - { - name: 'transfer_filter_contract', - cl_type: CLType?.Key(), - install: true, - optional: true, - }, - ], - }, - { - name: Tabs['CEP-85'], - types: [ - { ...nameArg, install: true, entry_point: true }, - { - name: 'uri', - cl_type: CLType?.String(), - install: true, - entry_point: true, - }, - { - name: 'events_mode', - cl_type: CLType.U8(), - install: true, - optional: true, - upgrade: true, - entry_point: true, - }, - { - name: 'enable_burn', - cl_type: CLType?.Bool(), - install: true, - optional: true, - entry_point: true, - }, - { - name: 'transfer_filter_contract', - cl_type: CLType?.Key(), - install: true, - optional: true, - }, - { - name: 'transfer_filter_method', - cl_type: CLType?.String(), - install: true, - optional: true, - }, - { - name: 'admin_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - entry_point: true, - }, - { - name: 'minter_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - entry_point: true, - }, - { - name: 'burner_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - entry_point: true, - }, - { - name: 'meta_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - entry_point: true, - }, - { - name: 'none_list', - cl_type: CLType?.List(CLType?.Key()), - install: true, - optional: true, - entry_point: true, - }, - { name: 'package_hash', cl_type: CLType?.Key(), upgrade: true }, - recipientArg, - ownerArg, - { name: 'id', cl_type: CLType?.U256() }, - { name: 'amount', cl_type: CLType?.U256() }, - { name: 'ids', cl_type: CLType?.List(CLType?.U256()) }, - { name: 'amounts', cl_type: CLType?.List(CLType?.U256()) }, - { name: 'account', cl_type: CLType?.Key() }, - { name: 'accounts', cl_type: CLType?.List(CLType?.Key()) }, - { name: 'operator', cl_type: CLType?.Key() }, - { name: 'approved', cl_type: CLType?.Bool() }, - { name: 'from', cl_type: CLType?.Key() }, - { name: 'to', cl_type: CLType?.Key() }, - { name: 'data', cl_type: CLType?.ByteArray() }, - { name: 'total_supply', cl_type: CLType?.U256() }, - { name: 'total_supplies', cl_type: CLType?.List(CLType?.U256()) }, - ], - }, +import { CepId } from './cep-schema.service'; + +/** Tab list for the Args builder (CEP-47 removed; CEP-95 added). */ +export const defaultTabs: { name: Tabs; cepId: CepId | null }[] = [ + { name: Tabs.Custom, cepId: null }, + { name: Tabs['CEP-18'], cepId: 'cep18' }, + { name: Tabs['CEP-78'], cepId: 'cep78' }, + { name: Tabs['CEP-85'], cepId: 'cep85' }, + { name: Tabs['CEP-95'], cepId: 'cep95' }, ]; - -export { customArg }; diff --git a/www/libs/feature/deployer/src/lib/put-deploy/put-deploy.component.ts b/www/libs/feature/deployer/src/lib/put-deploy/put-deploy.component.ts index 14e7a54..911252b 100644 --- a/www/libs/feature/deployer/src/lib/put-deploy/put-deploy.component.ts +++ b/www/libs/feature/deployer/src/lib/put-deploy/put-deploy.component.ts @@ -17,6 +17,7 @@ import { NamedCLTypeArg, State, extractEntryPoints, + parameterToNamedArg, } from '@casper-api/api-interfaces'; import { ResultService } from '../result/result.service'; import { Subscription } from 'rxjs'; @@ -546,34 +547,35 @@ export class PutDeployComponent implements AfterViewInit, OnDestroy { (entry_point: EntrypointsType) => entry_point['name'] === entry_point_value, ); - const args = entry_point?.['args']; - args && - this.storageService.setState({ - args: args as unknown as NamedCLTypeArg[], - entry_point: entry_point_value, - }); + const rawArgs = entry_point?.['args']; + const args = Array.isArray(rawArgs) + ? (rawArgs + .map((param: unknown) => parameterToNamedArg(param as { name?: string; cl_type?: unknown })) + .filter(Boolean) as NamedCLTypeArg[]) + : []; + this.storageService.setState({ + args, + entry_point: entry_point_value, + }); + this.deployerService.setState({ + args, + entry_point: entry_point_value, + }); } else { this.storageService.setState({ args: [], entry_point: '' }); + this.deployerService.setState({ args: [], entry_point: '' }); } } inputEntryPointChange($event: Event) { const entry_point_value = ($event.target as HTMLSelectElement).value; - if (!entry_point_value) { - this.storageService.setState({ args: [], entry_point: '' }); - } else { - this.storageService.setState({ entry_point: entry_point_value }); - } + this.updateArgs(entry_point_value || undefined); this.entryPoint = entry_point_value; } selectEntryPointChange($event: Event) { const entry_point_value = ($event.target as HTMLSelectElement).value; - if (!entry_point_value) { - this.storageService.setState({ args: [], entry_point: '' }); - } else { - this.updateArgs(entry_point_value); - } + this.updateArgs(entry_point_value || undefined); this.entryPoint = entry_point_value; } diff --git a/www/libs/ui/argument/src/lib/argument/argument.component.html b/www/libs/ui/argument/src/lib/argument/argument.component.html index 90cc9b7..3e273f4 100644 --- a/www/libs/ui/argument/src/lib/argument/argument.component.html +++ b/www/libs/ui/argument/src/lib/argument/argument.component.html @@ -7,11 +7,12 @@ defaultType['name'] + (defaultType['optional'] || defaultType['upgrade'] ? ' *' : '') " + (input)="onNameInput($event)" />