@arxiumlabs/sdk 0.8.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install -g @arxiumlabs/sdk # arx CLI
13
13
 
14
14
  ## Releasing
15
15
 
16
- Bump `version` here, run the release checks, then push a `sdk-vX.Y.Z` tag. `.github/workflows/sdk.yml` publishes to npm through trusted publishing (no token) with provenance.
16
+ Bump `version` here, run the release checks, then push a `sdk-vX.Y.Z` tag. `.github/workflows/sdk.yml` publishes to npm through trusted publishing (no token). It publishes without a provenance attestation while the repository is private, because npm accepts provenance only from public repositories.
17
17
 
18
18
  ```sh
19
19
  cd Arxium && cargo test -p arxd-runtime writes_typescript_signed_action_fixtures && git diff --exit-code sdk/ts/fixtures
@@ -44,10 +44,20 @@ const fee2 = actionFee(params, sender, nonce, payload, { signatureLength: multis
44
44
 
45
45
  ### Reading the chain
46
46
 
47
- `ArxiumRpc` returns typed responses for status, accounts, stake, assets (`listAssets`, `asset`, `assetByAlias`, `assetHolders`, `assetBalance`), attestors, validators (`validator`, `validatorPower`, `operatorValidators`), block effects, evidence and Merkle proofs. Each method takes a type parameter if you need a different shape.
47
+ The list methods (`listAssets`, `assetHolders`, `attestors`, `evidenceList`) follow the node's `next_cursor` pages (`?limit=&cursor=`) and return the whole list. `ArxiumRpc` returns typed responses for status, accounts, stake, assets (`listAssets`, `asset`, `assetByAlias`, `assetHolders`, `assetBalance`), attestors, validators (`validator`, `validatorPower`, `operatorValidators`), block effects, evidence and Merkle proofs. Each method takes a type parameter if you need a different shape.
48
+
49
+ `rpc.blockEffects(height)` and `rpc.effects(from, to)` carry `corporate_actions`: one `CorporateActionLine` per snapshot holder of every `DistributeToHolders`, `RedeemHolders` and `SplitAsset` in the block, with the action's `action_index` and `signature`, `asset`, `payout_asset` (distributions only), `holder`, `amount` (`bigint`) and `outcome` (`paid`, `withheld`, `minted`, `forced_back` or `redeemed`). Use these for per-holder payouts, not `asset_balances` diffs: those are end-of-block balances, so they blur a holder that moved twice in the block and show nothing for a withheld holder. Blocks a node wrote before it recorded these lines return an empty list.
50
+
51
+ They also carry `pledges`: one `PledgeEffect` per collateral pledge the block opened, changed or closed, with its `pledge` id and `record`, the end-of-block `Pledge`, or `null` once it was released or seized. Blocks from before nodes recorded pledges return an empty list.
48
52
 
49
53
  ### Sending
50
54
 
55
+ For pluggable asynchronous Ed25519 signing, use the verified signer/local
56
+ adapter with the existing RPC callback. [Contract, examples and limits](SIGNERS.md)
57
+ describe exact bytes, key matching, mutation protection and uncertain outcomes.
58
+ Keys held in an HSM or AWS KMS sign through `pkcs11Ed25519Signer` or
59
+ `awsKmsEd25519Signer`; see [HSM and KMS backends](SIGNERS.md#hsm-and-kms-backends).
60
+
51
61
  `sendAction` signs at the account's current nonce. The node's nonce doesn't count actions still in the mempool, so one client runs sends for the same sender one after another. Concurrent calls each wait their turn and never collide on a nonce. Two separate processes sending for one sender still need their own coordination. For a multisig or programmable-account sender, pass `sign(message, nonce)` instead of `privateKey` and return the combined witness.
52
62
 
53
63
  `rpc.sendAction({ privateKey, sender, payload })` signs, submits and waits for the block. If the chain refuses the action it throws `ActionDroppedError`, whose `reason` is the node's exact text. `rpc.assets(address)` returns the account's asset balances with `transfer_eligible` and `eligibility_reason`, so an app can check the sending side before the user signs. Walkthrough: https://arxium.network/docs/sdk-quickstart
@@ -73,6 +83,70 @@ A co-signer should see what they're signing before they sign it. `decodePayload(
73
83
 
74
84
  Set the resulting address as an admin role (genesis or `SetAdmin`) or as an asset issuer (`TransferIssuer`). To rotate members, move the role to a new multisig address.
75
85
 
86
+ ### Atomic DvP (delivery versus payment)
87
+
88
+ `SettleDvp` swaps two assets between two parties in one action: both legs settle or neither does. Each leg is `"native"` (IUM) or `{ asset }` (an RWA asset, with its full compliance gates, or a token). The counterparty signs the order first, at its current nonce. The initiator then sends it and pays the fee:
89
+
90
+ ```ts
91
+ const order: DvpOrder = { give: { asset: bondRef }, giveAmount: 10n, counterparty: buyer, take: { asset: cashRef }, takeAmount: 500n, expiresAt: height + 100n };
92
+ const consent = await signDvpConsent(buyerKey, genesis, seller, order, buyerNonce); // on the buyer's machine
93
+ await rpc.sendAction({ privateKey: sellerKey, sender: seller, payload: encodeSettleDvp(order, consent) });
94
+ ```
95
+
96
+ Settling consumes the counterparty's nonce, so the consent can't be replayed, and any other action the counterparty sends first cancels it. The node refuses the order after height `expiresAt`. A multisig counterparty's members each sign `dvpConsentBytes(…)` and combine them with `multisigSignature`. A programmable-account counterparty's owners each sign `dvpConsentBytes(…)` and combine them with `assemblePolicySignature("owner", policy, sigs)`; its leg is checked against its own spending limits and recipient allowlist (nodes after v0.15.0).
97
+
98
+ ### Venue settlement
99
+
100
+ A venue (an off-chain order book) settles trades between parties who each signed an order. Each order names the venue, so only that venue can settle it. The venue sends the action and pays the fee. One `encodeSettleVenue` carries 1 to `MAX_VENUE_TRADES` (32) trades, settled in order, all or nothing. In each trade the maker's give must equal the taker's take, same asset and amount, and the other way round:
101
+
102
+ ```ts
103
+ const sellTerms: VenueOrderTerms = { venue, give: { asset: bondRef }, giveAmount: 10n, take: { asset: cashRef }, takeAmount: 500n, expiresAt: height + 100n };
104
+ const buyTerms: VenueOrderTerms = { venue, give: { asset: cashRef }, giveAmount: 500n, take: { asset: bondRef }, takeAmount: 10n, expiresAt: height + 100n };
105
+ const maker: SignedVenueOrder = { owner: seller, orderId: sellId, terms: sellTerms, signature: await signVenueOrder(sellerKey, genesis, seller, sellId, sellTerms) }; // on the seller's machine
106
+ const taker: SignedVenueOrder = { owner: buyer, orderId: buyId, terms: buyTerms, signature: await signVenueOrder(buyerKey, genesis, buyer, buyId, buyTerms) }; // on the buyer's machine
107
+ await rpc.sendAction({ privateKey: venueKey, sender: venue, payload: encodeSettleVenue([{ maker, taker }]) });
108
+ ```
109
+
110
+ `orderId` is any u64 the owner hasn't used on a live order; pick a random one. Each `(owner, orderId)` settles at most once, there are no partial fills, and the owner's own actions don't touch its orders, so a party can have many live orders. An order may expire at most `MAX_ORDER_LIFETIME` (43,200 blocks, one day) past the height it settles at. The owner kills an order with `encodeCancelVenueOrder(orderId, expiresAt)`, passing the order's own expiry. A multisig or programmable-account owner's members each sign `venueOrderBytes(…)` and combine them as for `dvpConsentBytes(…)`. A programmable owner's leg is checked against its own spending limits and recipient allowlist.
111
+
112
+ ### Collateral pledges
113
+
114
+ A pledgor puts up `amount` of an asset for a beneficiary against an `exposure`, with a haircut, a valuation agent and a cure period. RWA and token units stay in the pledgor's balance, encumbered: the pledgor can't transfer, burn or deliver them. Native IUM moves into escrow. The beneficiary signs the terms first, at its current nonce, then the pledgor sends them:
115
+
116
+ ```ts
117
+ const terms: PledgeTerms = { beneficiary: bank, valuationAgent: null, asset: { asset: bondRef }, amount: 95n, haircutBps: 1_000, exposure: 50n, unitPrice: PRICE_SCALE, curePeriodBlocks: 10n, maturity: null };
118
+ const consent = await signPledgeConsent(bankKey, genesis, holder, terms, bankNonce); // on the bank's machine
119
+ await rpc.sendAction({ privateKey: holderKey, sender: holder, payload: encodePledge(terms, consent) });
120
+ const id = await pledgeId(holder, holderNonce); // the nonce the Pledge was sent at
121
+ ```
122
+
123
+ `unitPrice` is a price times `PRICE_SCALE`, so `PRICE_SCALE` is 1.00. `haircutBps: 1_000` values the collateral at 90%. `valuationAgent: null` makes the beneficiary the agent.
124
+
125
+ | Action | Sent by | Encoder |
126
+ |---|---|---|
127
+ | Mark a new price and exposure | the valuation agent | `encodeMarkPledge(id, unitPrice, exposure)` |
128
+ | Add collateral | the pledgor | `encodeTopUpPledge(id, amount)` |
129
+ | Swap the collateral | the pledgor, with the agent's `signSubstituteConsent(…)` | `encodeSubstitutePledge(id, asset, amount, unitPrice, agentSignature)` |
130
+ | Return collateral | the beneficiary any time; the pledgor in full once exposure is marked `0` | `encodeReleasePledge(id, amount)` |
131
+ | Seize in default | the beneficiary | `encodeSeizePledge(id, full)` |
132
+
133
+ A mark that values the collateral below the exposure opens a margin call. The pledgor has `curePeriodBlocks` to top up or substitute. After that, or after `maturity + curePeriodBlocks` with exposure still outstanding, the beneficiary may seize everything (`full: true`) or only the units worth the exposure, with the rest returned. `rpc.pledge(id)` returns the record with its `collateral_value`, `shortfall` and `in_default` at the tip. `rpc.accountAssetPledges(address, asset)` lists the pledges on one balance. Native collateral is escrowed, so it isn't listed there.
134
+
135
+ ### WASM modules
136
+
137
+ Asset modules (`docs/wasm-contracts-plan.md`) run inside the chain's compliance rules. They are off until a governed `SetChainParams` sets `modules_enabled`, so on a chain where it is off both actions are refused.
138
+
139
+ ```ts
140
+ const address = await moduleAddress(deployer, code, salt); // where it will live
141
+ await rpc.submit(submitBody(deployer, nonce, sig, encodeDeployModule(code, salt)));
142
+ const gas = 100_000n; // the fee follows this limit, not the gas used; set it from simulateModule's gas_used
143
+ await rpc.submit(submitBody(caller, nonce, sig, encodeCallModule(address, input, gas)));
144
+ await rpc.module(address); await rpc.moduleCode(address); await rpc.moduleState(address, keyHex);
145
+ const dry = await rpc.simulateModule(address, { caller, input, gasLimit: gas }); // { ok, gas_used, return, writes, events, balances } or { ok: false, error }
146
+ ```
147
+
148
+ Only an attested account can deploy. A module holds a regulated asset only if it is attested like any holder, and it pays out only from its own account through the same checks as a transfer (freeze, claims, jurisdiction, caps, locks). Limits per module: 32 KiB of code, 1 KiB per stored value, 16 reads, 16 writes, 16 lookups, 4 transfers per call. A call is priced by its gas limit twice over: execution (one weight per 50 fuel) and the dispute-artifact bytes it could add (15,000 weight per 1,000 fuel, up to a whole block at 64 rows), so a tight limit is cheap and a 1,000,000 limit fills a block. Deploying costs 8 weight per code byte. There is no way to list a module's storage; keep your own index and read it by key.
149
+
76
150
  ### Programmable accounts
77
151
 
78
152
  The SDK exports bounded account-policy codecs and stateful owner/session/guardian witness helpers. `arx multisig build|sign|combine|link` lets each member sign separately, for both immutable and stateful account addresses. To verify a historical action, `verifySignedAction` needs the proven pre-action policy for stateful witnesses, and `arx verify SIGNATURE --policy FILE` accepts that policy. See [the programmable-account guide](../../docs/Programmable_Accounts.md).
@@ -110,7 +184,7 @@ printf '%s' "$SEED_HEX" | arx keys import
110
184
  arx keys show --key arxium-devnet-key-abc.json # no passphrase needed
111
185
 
112
186
  arx sign transfer arx1recipient 1500000000 --key key.json # nonce fetched from --rpc
113
- arx sign transfer arx1recipient 1500000000 --key key.json --nonce 7 # fully offline
187
+ arx sign transfer arx1recipient 1500000000 --key key.json --nonce 7 # skips the nonce lookup; still reads the genesis hash from --rpc
114
188
  arx submit signed-action.json
115
189
  arx send transfer arx1recipient 1500000000 --key key.json
116
190
 
@@ -125,7 +199,9 @@ arx query search arx1address
125
199
  arx verify signature
126
200
  ```
127
201
 
128
- Supported signed actions are `transfer`, `stake`, `unstake`, `join-validator`, `leave-validator`, `register-bls-key`, `authorize-operator`, `revoke-operator`, `grant-attestation`, `revoke-attestation`, `register-asset`, `issue-asset`, `transfer-asset`, `apply-attestor`, `block-attestor`, `propose-add-attestor`, `propose-remove-attestor`, `propose-unblock-attestor`, `vote` and `execute-proposal`, plus the issuer, corporate-action and token commands below. Amount arguments are raw base-unit integers (IUM for ARX, the asset's smallest unit for an asset).
202
+ `arx sign` always needs a reachable node. Every signature covers the chain's genesis hash (see Chain binding above), and `sign` reads it from `--rpc` (`GET /genesis-hash`) on every run. `--nonce` only skips the account-nonce lookup. There is no fully offline signing mode: with no node the command exits 1 with the fetch error.
203
+
204
+ Supported signed actions are `transfer`, `stake`, `unstake`, `join-validator`, `leave-validator`, `register-bls-key`, `authorize-operator`, `revoke-operator`, `grant-attestation`, `revoke-attestation`, `register-asset`, `issue-asset`, `transfer-asset`, `apply-attestor`, `block-attestor`, `propose-add-attestor`, `propose-remove-attestor`, `propose-unblock-attestor`, `vote`, `execute-proposal`, `settle-dvp`, `venue-settle`, `venue-cancel`, `module-deploy` and `module-call`, plus the issuer, corporate-action and token commands below. Amount arguments are raw base-unit integers (IUM for ARX, the asset's smallest unit for an asset).
129
205
 
130
206
  ```sh
131
207
  arx send apply-attestor "Arxium Bank" <owner>,<owner>,<owner> 2 <evidence-hash> <evidence-uri> # sender is the 2-of-N multisig; validators vote
@@ -154,6 +230,16 @@ arx send redeem-holders arxasset1... <height>
154
230
  arx send split-asset arxasset1... <height> 2 1
155
231
  arx send create-token ABC "Abc Token" 6 1000000 - mintable # <max-supply|-> mintable|fixed
156
232
  arx send mint-token arxasset1... arx1to 100 # transfer-token, burn-token, renounce-mint
233
+ arx dvp-consent arx1seller arxasset1bond... 10 native 500 <expires-at-height> --key buyer.json # buyer signs; prints the "send" args
234
+ arx send settle-dvp arxasset1bond... 10 arx1buyer native 500 <expires-at-height> <buyer-signature> --key seller.json
235
+ arx venue-order arx1venue arxasset1bond... 10 arxasset1cash... 500 <expires-at-height> --key seller.json > sell.json # random order id, or --order-id n
236
+ arx send venue-settle trades.json --key venue.json # [{ "maker": <sell.json>, "taker": <buy.json> }, ...]
237
+ arx send venue-cancel <order-id> <expires-at-height> --key seller.json
238
+ arx send module-deploy <module.wasm> [salt] --key deployer.json
239
+ arx send module-call <module-address> <gas-limit> [input-hex] --key caller.json
240
+ arx query module <address>
241
+ arx query module-state <address> <key-hex>
242
+ arx simulate <module-address> <gas-limit> [input-hex] --key caller.json # dry run, nothing is sent
157
243
 
158
244
  arx query assets [arx1address] # all assets, or one account's balances
159
245
  arx query asset arxasset1...
@@ -175,7 +261,7 @@ public entry point and checks the CLI version; it makes no chain calls.
175
261
 
176
262
  Publication runs only for an approved `sdk-vX.Y.Z` tag matching package.json.
177
263
  The publish job builds its own checkout, checks the packed files and smoke-tests
178
- the tarball, then publishes that exact tarball with provenance. Existing npm
264
+ the tarball, then publishes that exact tarball (no provenance while the repo is private). Existing npm
179
265
  versions cannot be replaced. npm trusted-publisher configuration and release
180
266
  authorization are still required; a passing package check is not publication
181
267
  or on-chain compatibility proof.
package/SIGNERS.md ADDED
@@ -0,0 +1,132 @@
1
+ # Asynchronous signing contract
2
+
3
+ The SDK already accepts `ArxiumRpc.sendAction({ sign })` for custom witnesses.
4
+ The verified Ed25519 interface plugs into that callback and also signs offline
5
+ actions and DvP consents. It adds no provider, dependency, key storage or network
6
+ request. Existing CryptoKey functions and the generic witness callback remain
7
+ available. RPC now captures the caller's options and payload at invocation,
8
+ including requests waiting in the per-sender queue.
9
+
10
+ ```ts
11
+ import {
12
+ localEd25519Signer, signActionWithSigner, verifiedSignerCallback,
13
+ encodeAddress, type ArxiumRpc,
14
+ } from "@arxiumlabs/sdk";
15
+
16
+ // The public key is from your already verified key file/account configuration.
17
+ async function example(privateKey: CryptoKey, publicKey: Uint8Array,
18
+ genesis: Uint8Array, nonce: bigint,
19
+ payload: Uint8Array, rpc: ArxiumRpc) {
20
+ const signer = localEd25519Signer(privateKey, publicKey);
21
+ const sender = encodeAddress(publicKey);
22
+ const signature = await signActionWithSigner(signer, publicKey, genesis, sender, nonce, payload);
23
+ // An explicit send, only when the caller intends a transaction:
24
+ // await rpc.sendAction({ sender, payload, sign: verifiedSignerCallback(signer, publicKey) });
25
+ return signature;
26
+ }
27
+ ```
28
+
29
+ `Ed25519Signer` advertises `algorithm: "Ed25519"`, a raw32-byte compressed
30
+ public key and `sign(message): Promise<Uint8Array>` returning a raw64-byte
31
+ signature. The mode is **pure Ed25519 over the exact supplied message**.
32
+ No SDK prehash, double hash, DER/PEM wrapper, Ed25519ph/ctx or provider digest
33
+ mode is implied. A future backend must establish its actual mode/encoding;
34
+ different HSM/KMS products cannot be assumed to support this same operation.
35
+ The local adapter uses the existing WebCrypto Ed25519 primitive and never
36
+ exports private key material. It does not sign a probe at construction.
37
+
38
+ `signBytesWithSigner` snapshots the message and caller-selected expected key,
39
+ checks the advertised key/algorithm, invokes the provider once with a separate
40
+ buffer, snapshots the returned signature, then verifies it against the original
41
+ message and expected key before returning lowercase hex. The expected key must
42
+ come from trusted account/member configuration, not an unchecked provider claim.
43
+ The callback factory pins key bytes and the method at creation. Provider-side
44
+ mutation cannot change the trusted message; a signature of changed or hashed
45
+ bytes is refused. Each concurrent call owns separate state and buffers.
46
+
47
+ `signActionWithSigner` uses existing signingBytes: raw32-byte genesis, string
48
+ sender, u64 nonce, existing payload bytes. `signDvpConsentWithSigner` uses
49
+ existing dvpConsentBytes and its separate DvP domain/counterparty nonce. Amounts
50
+ remain bigint/u128 in their existing encoders; these helpers do not reinterpret
51
+ payload fields. Nonces accept exact bigint through u64 max, and safe integer
52
+ Numbers; unsafe values fail under A120. RPC's existing numeric nonce API still
53
+ rejects unsafe Numbers; this does not add full-u64 JSON RPC support.
54
+
55
+ For multisig/programmable accounts, sender is the stable account, while the
56
+ expected key is the chosen member key. These helpers return one member's raw
57
+ signature. Use existing multisigSignature/assemblePolicySignature tooling to
58
+ assemble a witness. Signature verification does not establish current policy,
59
+ membership, nonce freshness, balances, expiry, authorization or finality. The
60
+ verified callback directly returns a single-key signature; a custom callback
61
+ is still needed to assemble multi-member/policy witnesses before submission.
62
+
63
+ Provider errors/refusal/uncertain outcomes propagate unchanged. Invalid key,
64
+ mode, response shape or signature raises SignerVerificationError. There is no
65
+ fallback, retry, second signature, provider failover or implicit submission in
66
+ these helpers. They do not offer signing timeout/cancellation. A pending signer
67
+ may finish late; a caller's Promise.race or abandoned wait cannot cancel remote
68
+ work; abandoning an entire RPC send also does not cancel that still-pending
69
+ send or prevent its one originally requested submission. Once a provider promise rejects, a later resolve is ignored and does not
70
+ trigger submission. An explicit RPC send signs/submits once; submission errors
71
+ and the existing post-submit polling timeout propagate without automatic resend.
72
+ RPC timeoutMs begins after submission and does not interrupt signing or cancel a
73
+ transaction. A failed send rejects requests already queued for that sender
74
+ with the same error; they do not sign or submit after the failure. This changes
75
+ the old queue behavior that continued automatically after any error. Once the
76
+ queue unwinds, a new explicit request is possible. Reconcile an ambiguous remote
77
+ signing/submission outcome before any such separately authorized request. Per-sender queuing is local to one RPC
78
+ client, not a chain lock or cross-client nonce reservation.
79
+
80
+ ## HSM and KMS backends
81
+
82
+ Arxium signs with pure Ed25519. The SDK adds no dependency for either backend:
83
+ you pass in a client you have already configured, and the private key never leaves the device.
84
+
85
+ | Backend | Key | Mode | Status |
86
+ |---|---|---|---|
87
+ | PKCS#11 v3 (`pkcs11Ed25519Signer`) | `CKK_EC_EDWARDS`, Ed25519 | `CKM_EDDSA`, no parameters | Devnet E2E on SoftHSM 2.7 |
88
+ | AWS KMS (`awsKmsEd25519Signer`) | `ECC_NIST_EDWARDS25519` | `ED25519_SHA_512`, `MessageType: RAW` | Request shape unit-tested, no live KMS run yet |
89
+ | GCP Cloud KMS | `EC_SIGN_ED25519` | PureEdDSA | Software protection level only, not HSM: no adapter |
90
+ | Azure Key Vault / Managed HSM | no Ed25519 | | Not possible |
91
+
92
+ PKCS#11 tokens with Ed25519 include YubiHSM 2, Thales Luna 7.7+, Utimaco and
93
+ SoftHSM 2.6+. Check your firmware: `CKM_EDDSA` arrived with PKCS#11 3.0.
94
+
95
+ ```ts
96
+ import { KMS } from "@aws-sdk/client-kms";
97
+ const signer = await awsKmsEd25519Signer(new KMS({ region: "eu-west-1" }), "alias/treasury");
98
+ await rpc.sendAction({ sender: encodeAddress(trustedKey), payload, sign: verifiedSignerCallback(signer, trustedKey) });
99
+ ```
100
+
101
+ ```ts
102
+ import pkcs11js from "pkcs11js"; // logged-in session; key handles from C_FindObjects
103
+ const signer = pkcs11Ed25519Signer(p11, session, privateKeyHandle, publicKeyHandle);
104
+ ```
105
+
106
+ The adapters work with the verified helpers above. Pin `trustedKey` from your own
107
+ configuration, not from what the backend reports. For multisig, give each member its own
108
+ signer (one HSM, another KMS) and assemble with `multisigSignature`.
109
+
110
+ Limits:
111
+ - AWS KMS signs RAW messages of at most 4096 bytes (`AWS_KMS_MAX_MESSAGE`). Pure Ed25519
112
+ cannot be prehashed, so the adapter refuses a larger action before calling KMS.
113
+ `ED25519_PH_SHA_512` produces signatures the chain does not accept.
114
+ - A PKCS#11 session runs one operation at a time. The adapter's init and sign run
115
+ back to back synchronously, so calls on one session never interleave.
116
+
117
+ `scripts/pkcs11-e2e.mjs` generates a non-extractable key on a PKCS#11 token, funds it
118
+ from a devnet key and sends a transfer signed on the token. To run it on SoftHSM:
119
+
120
+ ```sh
121
+ brew install softhsm && softhsm2-util --init-token --free --label arx --pin 1234 --so-pin 5678
122
+ npm run build && npm i --no-save pkcs11js
123
+ PKCS11_LIB=/opt/homebrew/lib/softhsm/libsofthsm2.so PKCS11_PIN=1234 ARX_RPC=… ARX_TOKEN=… ARX_FUND_SEED=… node scripts/pkcs11-e2e.mjs
124
+ ```
125
+
126
+ Evidence: real WebCrypto signatures checked against Rust-generated action and
127
+ DvP vectors; adversarial buffers, wrong key/mode/signature, prehashed input,
128
+ refusal, ambiguous/late completion and concurrent requests; actual RPC/legacy/
129
+ multisig paths use injected synthetic fetch. These are source and synthetic
130
+ integration tests, **not HSM/KMS or production confirmation evidence**. The
131
+ PKCS#11 adapter has run end to end on devnet against SoftHSM. AWS KMS has not
132
+ run against a live key yet.
package/dist/actions.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { POLICY_VARIANT } from "./policy.js";
1
+ import { POLICY_VARIANT, type SpendAsset } from "./policy.js";
2
2
  /** These indices are ActionPayload's positional bincode discriminants. Never derive them. */
3
3
  export declare const ACTION_VARIANT: {
4
4
  readonly transfer: 0;
@@ -39,6 +39,9 @@ export declare const ACTION_VARIANT: {
39
39
  readonly executeProposal: 32;
40
40
  readonly applyAttestor: 41;
41
41
  readonly blockAttestor: 42;
42
+ readonly settleDvp: 43;
43
+ readonly deployModule: 46;
44
+ readonly callModule: 47;
42
45
  };
43
46
  /** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
44
47
  export declare const TOKEN_ACTION = 39;
@@ -137,6 +140,129 @@ export declare function encodeRenounceMint(tokenRef: string): Uint8Array;
137
140
  export declare function encodeApplyAttestor(name: string, owners: string[], threshold: number, evidenceHash: string, evidenceUri: string): Uint8Array;
138
141
  /** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
139
142
  export declare function encodeBlockAttestor(): Uint8Array;
143
+ /**
144
+ * Atomic delivery-versus-payment. The sender (initiator) delivers `giveAmount` of `give` to `counterparty`, who delivers
145
+ * `takeAmount` of `take` back, both legs or neither. `"native"` is IUM; `{ asset }` is an RWA asset or a token ref.
146
+ */
147
+ export type DvpOrder = {
148
+ give: SpendAsset;
149
+ giveAmount: bigint;
150
+ counterparty: string;
151
+ take: SpendAsset;
152
+ takeAmount: bigint;
153
+ expiresAt: bigint;
154
+ };
155
+ /** `DvpTerms.domain`: no `ActionPayload` can start with it, so a consent never doubles as an ordinary action signature. */
156
+ export declare const DVP_DOMAIN = 4294967295;
157
+ /**
158
+ * What the counterparty signs, mirroring `dvp_consent(…).signing_bytes`: the order, bound to the genesis hash and the
159
+ * counterparty's current nonce, which settling consumes. Sending any other action first cancels the consent. A multisig
160
+ * counterparty's members each sign these bytes and combine them with `multisigSignature`; a programmable account's owners
161
+ * combine theirs with `assemblePolicySignature("owner", …)`.
162
+ */
163
+ export declare function dvpConsentBytes(genesis: Uint8Array, initiator: string, order: DvpOrder, counterpartyNonce: number | bigint): Uint8Array;
164
+ export declare function signDvpConsent(privateKey: CryptoKey, genesis: Uint8Array, initiator: string, order: DvpOrder, counterpartyNonce: number | bigint): Promise<string>;
165
+ export declare function encodeSettleDvp(order: DvpOrder, counterpartySignature: string): Uint8Array;
166
+ /** `ActionPayload::Collateral(CollateralAction)` is variant 44; these are `CollateralAction`'s own discriminants, written after it. */
167
+ export declare const COLLATERAL_ACTION = 44;
168
+ export declare const COLLATERAL_VARIANT: {
169
+ readonly pledge: 0;
170
+ readonly markPledge: 1;
171
+ readonly topUpPledge: 2;
172
+ readonly substitutePledge: 3;
173
+ readonly releasePledge: 4;
174
+ readonly seizePledge: 5;
175
+ };
176
+ /** `unitPrice` is quote units per base unit of the pledged asset, times this: `PRICE_SCALE` is a price of 1.00. */
177
+ export declare const PRICE_SCALE = 1000000000n;
178
+ /** `PledgeConsent.domain` and `SubstituteConsent.domain`: like `DVP_DOMAIN`, no consent doubles as an action signature. */
179
+ export declare const PLEDGE_DOMAIN = 4294967294;
180
+ export declare const SUBSTITUTE_DOMAIN = 4294967293;
181
+ /**
182
+ * The sender pledges `amount` of `asset` to `beneficiary` against `exposure`. `valuationAgent: null` makes the beneficiary the
183
+ * agent, the only account that may `markPledge`. A mark below the exposure opens a margin call the pledgor has
184
+ * `curePeriodBlocks` to cure; past it, or past `maturity + curePeriodBlocks` while exposure is outstanding, the beneficiary may seize.
185
+ */
186
+ export type PledgeTerms = {
187
+ beneficiary: string;
188
+ valuationAgent: string | null;
189
+ asset: SpendAsset;
190
+ amount: bigint;
191
+ haircutBps: number;
192
+ exposure: bigint;
193
+ unitPrice: bigint;
194
+ curePeriodBlocks: bigint;
195
+ maturity: bigint | null;
196
+ };
197
+ /** Mirrors `pledge_id`: `sha256("xc-pledge:" ‖ pledgor ‖ ":" ‖ nonce as 8 big-endian bytes)`, where `nonce` is the opening `Pledge`'s. */
198
+ export declare function pledgeId(pledgor: string, nonce: number | bigint): Promise<string>;
199
+ /**
200
+ * What the beneficiary signs, mirroring `pledge_consent(…).signing_bytes`: the terms, bound to the genesis hash and the
201
+ * beneficiary's current nonce, which the pledge consumes. Multisig and programmable beneficiaries combine signatures as for `dvpConsentBytes`.
202
+ */
203
+ export declare function pledgeConsentBytes(genesis: Uint8Array, pledgor: string, terms: PledgeTerms, beneficiaryNonce: number | bigint): Uint8Array;
204
+ export declare function signPledgeConsent(privateKey: CryptoKey, genesis: Uint8Array, pledgor: string, terms: PledgeTerms, beneficiaryNonce: number | bigint): Promise<string>;
205
+ export declare function encodePledge(terms: PledgeTerms, beneficiarySignature: string): Uint8Array;
206
+ /** The valuation agent's new price and exposure: opens a margin call on a shortfall, closes one once covered. Exposure `0` marks the debt repaid. */
207
+ export declare function encodeMarkPledge(pledge: string, unitPrice: bigint, exposure: bigint): Uint8Array;
208
+ /** The pledgor adds `amount` of the pledged asset. */
209
+ export declare function encodeTopUpPledge(pledge: string, amount: bigint): Uint8Array;
210
+ /** What the valuation agent signs for a substitution, mirroring `substitute_consent(…).signing_bytes`, at the agent's nonce (consumed). */
211
+ export declare function substituteConsentBytes(genesis: Uint8Array, agent: string, agentNonce: number | bigint, pledge: string, asset: SpendAsset, amount: bigint, unitPrice: bigint): Uint8Array;
212
+ export declare function signSubstituteConsent(privateKey: CryptoKey, genesis: Uint8Array, agent: string, agentNonce: number | bigint, pledge: string, asset: SpendAsset, amount: bigint, unitPrice: bigint): Promise<string>;
213
+ /** The pledgor swaps the collateral for `amount` of `asset` at `unitPrice`, with the agent's `signSubstituteConsent`. */
214
+ export declare function encodeSubstitutePledge(pledge: string, asset: SpendAsset, amount: bigint, unitPrice: bigint, agentSignature: string): Uint8Array;
215
+ /** `amount` back to the pledgor: the beneficiary at any time, the pledgor in full once the exposure is marked repaid. */
216
+ export declare function encodeReleasePledge(pledge: string, amount: bigint): Uint8Array;
217
+ /** In default only, by the beneficiary: everything (`full`) or the units worth the exposure, the rest returned. Closes the pledge. */
218
+ export declare function encodeSeizePledge(pledge: string, full: boolean): Uint8Array;
219
+ /** `ActionPayload::Venue(VenueAction)` is variant 45; these are `VenueAction`'s own discriminants, written after it. */
220
+ export declare const VENUE_ACTION = 45;
221
+ export declare const VENUE_VARIANT: {
222
+ readonly settleVenue: 0;
223
+ readonly cancelVenueOrder: 1;
224
+ };
225
+ /** `VenueOrder.domain`: like `DVP_DOMAIN`, an order signature never doubles as an action or a consent. */
226
+ export declare const VENUE_ORDER_DOMAIN = 4294967292;
227
+ /** Blocks an order may expire ahead of the height it settles at (one day at 2 s blocks). `signVenueOrder` callers should stay within it. */
228
+ export declare const MAX_ORDER_LIFETIME = 43200n;
229
+ /** Trades per `settleVenue`. */
230
+ export declare const MAX_VENUE_TRADES = 32;
231
+ /** One side of a trade: give `giveAmount` of `give` for `takeAmount` of `take`, settled only by `venue`, until `expiresAt`. */
232
+ export type VenueOrderTerms = {
233
+ venue: string;
234
+ give: SpendAsset;
235
+ giveAmount: bigint;
236
+ take: SpendAsset;
237
+ takeAmount: bigint;
238
+ expiresAt: bigint;
239
+ };
240
+ export type SignedVenueOrder = {
241
+ owner: string;
242
+ orderId: bigint;
243
+ terms: VenueOrderTerms;
244
+ signature: string;
245
+ };
246
+ /**
247
+ * What an order's owner signs, mirroring `venue_order(…).signing_bytes`: the terms as an action from the owner with
248
+ * `nonce = orderId`. Each `(owner, orderId)` settles at most once; pick a random u64. Multisig and programmable owners
249
+ * combine signatures as for `dvpConsentBytes`.
250
+ */
251
+ export declare function venueOrderBytes(genesis: Uint8Array, owner: string, orderId: number | bigint, terms: VenueOrderTerms): Uint8Array;
252
+ export declare function signVenueOrder(privateKey: CryptoKey, genesis: Uint8Array, owner: string, orderId: number | bigint, terms: VenueOrderTerms): Promise<string>;
253
+ /** The venue (sender, named in every order) settles 1..`MAX_VENUE_TRADES` mirrored trades, all or nothing, and pays the fee. */
254
+ export declare function encodeSettleVenue(trades: {
255
+ maker: SignedVenueOrder;
256
+ taker: SignedVenueOrder;
257
+ }[]): Uint8Array;
258
+ /** The owner kills its order. `expiresAt` must be the order's own expiry: the chain forgets the cancel after it. */
259
+ export declare function encodeCancelVenueOrder(orderId: number | bigint, expiresAt: number | bigint): Uint8Array;
260
+ /** Deploys a WASM asset module. Refused unless the chain has `modules_enabled`, and then only for an attested sender. Its address is `moduleAddress(sender, sha256(code), salt)` on the node; read it back from `GET /modules/{address}`. */
261
+ export declare function encodeDeployModule(code: Uint8Array, salt?: number | bigint): Uint8Array;
262
+ /** Calls a module's `call` export. The fee is set by `gasLimit`, not by the gas used, so keep it close to what the call needs. */
263
+ export declare function encodeCallModule(module: string, input: Uint8Array, gasLimit: number | bigint): Uint8Array;
264
+ /** Mirrors `module_address` in `arxd/runtime/src/module.rs`: where a module deployed by `deployer` with this `code` and `salt` lives. */
265
+ export declare function moduleAddress(deployer: string, code: Uint8Array, salt?: number | bigint): Promise<string>;
140
266
  /** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
141
267
  export declare const GOVERNANCE_ACTION: {
142
268
  readonly addAttestor: 4;
@@ -163,7 +289,7 @@ export declare function encodeVoteProposal(proposal: number | bigint, approve: b
163
289
  export declare function encodeExecuteProposal(proposal: number | bigint): Uint8Array;
164
290
  export declare function encodeSetAssetLimits(asset: string, maxHolders: number | null, maxBalancePerHolder?: bigint | null, maxAttestationAge?: bigint | null): Uint8Array;
165
291
  /** `{ name, input }` names and shapes match `fixtures/signed-actions.json`: amounts are decimal strings, byte fields are number arrays. */
166
- export type ActionName = keyof typeof ACTION_VARIANT | keyof typeof TOKEN_VARIANT | keyof typeof POLICY_VARIANT;
292
+ export type ActionName = keyof typeof ACTION_VARIANT | keyof typeof TOKEN_VARIANT | keyof typeof COLLATERAL_VARIANT | keyof typeof VENUE_VARIANT | keyof typeof POLICY_VARIANT;
167
293
  export type DecodedPayload = {
168
294
  name: ActionName;
169
295
  input: Record<string, any>;