@arxiumlabs/sdk 0.5.0 → 0.9.0

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
@@ -29,7 +29,28 @@ const rpc = new ArxiumRpc({ rpc: "https://node.example", token: process.env.ARX_
29
29
  const payload = encodeTransfer("arx1...", arxToIum("1.5"));
30
30
  ```
31
31
 
32
- All token amounts are `bigint`: `1 ARX = 1_000_000_000 IUM`.
32
+ All token amounts are `bigint`: `1 ARX = 1_000_000_000 IUM`. That includes amounts in RPC responses (`balance`, `total_supply`, `frozen_amount`, …): the node writes u128 amounts as plain JSON numbers, so the client parses them with `parseJson`, which never rounds them. `JSON.stringify` throws on a `bigint`, so pass `bigintReplacer` when you serialize a response.
33
+
34
+ ### Fees
35
+
36
+ An action costs `action_fee + weight × weight_fee`. The weight is the action type's base weight plus the signed action's size in bytes, plus 5,000 when a stateful account policy signs it. `actionFee` matches the node's metering exactly, and the tests check it against weights the node itself produced:
37
+
38
+ ```ts
39
+ const fee = actionFee(await rpc.feeParams(), sender, nonce, payload); // single-key signature
40
+ const fee2 = actionFee(params, sender, nonce, payload, { signatureLength: multisigSignatureLength(2, 3) }); // before a 2-of-3 multisig signs
41
+ ```
42
+
43
+ `CreateToken` also pays `token_create_fee` (`rpc.actionFee()`). `ACTION_FEE` is deprecated, because it is only the flat floor.
44
+
45
+ ### Reading the chain
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.
48
+
49
+ ### Sending
50
+
51
+ `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
+
53
+ `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
33
54
 
34
55
  ### Chain binding
35
56
 
@@ -52,6 +73,22 @@ A co-signer should see what they're signing before they sign it. `decodePayload(
52
73
 
53
74
  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.
54
75
 
76
+ ### Atomic DvP (delivery versus payment)
77
+
78
+ `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:
79
+
80
+ ```ts
81
+ const order: DvpOrder = { give: { asset: bondRef }, giveAmount: 10n, counterparty: buyer, take: { asset: cashRef }, takeAmount: 500n, expiresAt: height + 100n };
82
+ const consent = await signDvpConsent(buyerKey, genesis, seller, order, buyerNonce); // on the buyer's machine
83
+ await rpc.sendAction({ privateKey: sellerKey, sender: seller, payload: encodeSettleDvp(order, consent) });
84
+ ```
85
+
86
+ 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 is not supported yet.
87
+
88
+ ### Programmable accounts
89
+
90
+ 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).
91
+
55
92
  ## Key files
56
93
 
57
94
  `arx` reads and writes Console's encrypted export JSON unchanged:
@@ -85,7 +122,7 @@ printf '%s' "$SEED_HEX" | arx keys import
85
122
  arx keys show --key arxium-devnet-key-abc.json # no passphrase needed
86
123
 
87
124
  arx sign transfer arx1recipient 1500000000 --key key.json # nonce fetched from --rpc
88
- arx sign transfer arx1recipient 1500000000 --key key.json --nonce 7 # fully offline
125
+ arx sign transfer arx1recipient 1500000000 --key key.json --nonce 7 # skips the nonce lookup; still reads the genesis hash from --rpc
89
126
  arx submit signed-action.json
90
127
  arx send transfer arx1recipient 1500000000 --key key.json
91
128
 
@@ -100,30 +137,51 @@ arx query search arx1address
100
137
  arx verify signature
101
138
  ```
102
139
 
103
- 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`. Amount arguments are raw base-unit integers (IUM for ARX, the asset's smallest unit for an asset).
140
+ `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.
141
+
142
+ 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` and `settle-dvp`, 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).
104
143
 
105
144
  ```sh
106
145
  arx send apply-attestor "Arxium Bank" <owner>,<owner>,<owner> 2 <evidence-hash> <evidence-uri> # sender is the 2-of-N multisig; validators vote
107
146
  arx send propose-remove-attestor <attestor> "reason" # validators only; blocks that attestor's grants while the vote runs
108
147
  arx send vote <proposal-id> yes # then, after the window: arx send execute-proposal <proposal-id>
109
148
  arx send block-attestor # attestor emergency stop; only a governance vote lifts it
110
- arx send register-asset fund-a FUNDA "Fund A" 0 kyc CH,LI # <asset-id> <symbol> <name> <decimals> [topics] [jurisdictions]
149
+ arx send register-asset fund-a FUNDA "Fund A" 0 kyc CH,LI # <asset-id> <symbol> <name> <decimals> [topics] [jurisdictions] [controls]
150
+ arx send register-asset fund-b FUNDB "Fund B" 0 - - freeze_asset,recover_holder # only these issuer controls, forever
111
151
  arx send issue-asset arxasset1... 1000
112
152
  arx send transfer-asset arxasset1... arx1recipient 100
153
+ arx send set-asset-limits arxasset1... - 150 - # <max-holders|-> [max-balance-per-holder|-] [max-attestation-age-blocks|-]; - = no limit
154
+ arx send freeze-holder arxasset1... arx1holder # unfreeze-holder to lift
155
+ arx send freeze-asset arxasset1... "reason" # unfreeze-asset arxasset1... "reason" to lift
156
+ arx send issue-asset-to arxasset1... arx1holder 100
157
+ arx send lock-holder-amount arxasset1... arx1holder 50 [until-height] # unlock-holder-amount to release
158
+ arx send forced-transfer arxasset1... arx1from arx1to 10 "court order"
159
+ arx send recover-holder arxasset1... arx1lost arx1replacement
160
+ arx send burn-asset arxasset1... 10
161
+ arx send lock-issuance arxasset1... # one-way
162
+ arx send transfer-issuer arxasset1... arx1newissuer
163
+ arx send set-metadata-uri arxasset1... ipfs://... # - clears it
164
+ arx send set-private-claims arxasset1... on|off
165
+ arx send snapshot-holders arxasset1... # then, by the snapshot height:
166
+ arx send distribute arxasset1... <height> arxasset1payout... 1000
167
+ arx send redeem-holders arxasset1... <height>
168
+ arx send split-asset arxasset1... <height> 2 1
169
+ arx send create-token ABC "Abc Token" 6 1000000 - mintable # <max-supply|-> mintable|fixed
170
+ arx send mint-token arxasset1... arx1to 100 # transfer-token, burn-token, renounce-mint
171
+ arx dvp-consent arx1seller arxasset1bond... 10 native 500 <expires-at-height> --key buyer.json # buyer signs; prints the "send" args
172
+ arx send settle-dvp arxasset1bond... 10 arx1buyer native 500 <expires-at-height> <buyer-signature> --key seller.json
173
+
174
+ arx query assets [arx1address] # all assets, or one account's balances
175
+ arx query asset arxasset1...
176
+ arx query holders arxasset1...
177
+ arx query balance arx1address arxasset1...
178
+ arx query attestors # attestor <address>
179
+ arx query validator arx1address
180
+ arx query stakes arx1address
181
+ arx query fee
113
182
  ```
114
183
 
115
184
  Builder docs (devnet quickstart, RWA walkthrough, arx-verify): https://arxium.network/docs
116
- # Programmable-account tooling (0.1.5)
117
-
118
- The SDK exports bounded account-policy codecs and stateful owner/session/guardian
119
- witness helpers. `arx multisig build|sign|combine|link` provides an individual
120
- member signing workflow for both immutable and stateful account addresses.
121
- Historical `verifySignedAction` calls require the proven pre-action policy for
122
- stateful witnesses; `arx verify SIGNATURE --policy FILE` accepts that policy.
123
-
124
- See [the programmable-account guide](../../docs/Programmable_Accounts.md) for
125
- configuration shapes, fees, block-height windows, recovery and reset requirements.
126
-
127
185
  ## Release validation
128
186
 
129
187
  CI builds and installs the packed SDK on pull requests. Run `npm ci`,
@@ -133,7 +191,7 @@ public entry point and checks the CLI version; it makes no chain calls.
133
191
 
134
192
  Publication runs only for an approved `sdk-vX.Y.Z` tag matching package.json.
135
193
  The publish job builds its own checkout, checks the packed files and smoke-tests
136
- the tarball, then publishes that exact tarball with provenance. Existing npm
194
+ the tarball, then publishes that exact tarball (no provenance while the repo is private). Existing npm
137
195
  versions cannot be replaced. npm trusted-publisher configuration and release
138
196
  authorization are still required; a passing package check is not publication
139
197
  or on-chain compatibility proof.
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,7 @@ export declare const ACTION_VARIANT: {
39
39
  readonly executeProposal: 32;
40
40
  readonly applyAttestor: 41;
41
41
  readonly blockAttestor: 42;
42
+ readonly settleDvp: 43;
42
43
  };
43
44
  /** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
44
45
  export declare const TOKEN_ACTION = 39;
@@ -66,6 +67,13 @@ declare const TOPIC: {
66
67
  };
67
68
  export type AssetClass = keyof typeof CLASS;
68
69
  export type ClaimTopic = keyof typeof TOPIC;
70
+ /** `IssuerControls` field order — positional bincode, append only. Fixed for the asset's life once registered. */
71
+ export declare const ISSUER_CONTROLS: readonly ["freeze_asset", "freeze_holder", "lock_holder_amount", "forced_transfer", "recover_holder", "redeem", "split", "set_limits", "set_metadata_uri"];
72
+ export type IssuerControl = (typeof ISSUER_CONTROLS)[number];
73
+ export type IssuerControls = Record<IssuerControl, boolean>;
74
+ /** Every power on, the chain's default (what assets had before controls were selectable). */
75
+ export declare const ALL_ISSUER_CONTROLS: IssuerControls;
76
+ /** `controls` omitted means every power on, matching the node's default. */
69
77
  export type AssetMetadata = {
70
78
  asset_class: AssetClass;
71
79
  decimals: number;
@@ -75,7 +83,9 @@ export type AssetMetadata = {
75
83
  metadata_uri: string | null;
76
84
  symbol: string;
77
85
  name: string;
86
+ controls?: IssuerControls;
78
87
  };
88
+ /** @deprecated The flat floor only. The node charges `action_fee + weight × weight_fee`: use `actionFee(await rpc.feeParams(), …)`. */
79
89
  export declare const ACTION_FEE = 1000000n;
80
90
  export declare function encodeTransfer(to: string, amount: bigint): Uint8Array;
81
91
  export declare function encodeJoinValidator(validator: string, stake: bigint, blsPubkey: Uint8Array, blsPop: Uint8Array): Uint8Array;
@@ -128,6 +138,28 @@ export declare function encodeRenounceMint(tokenRef: string): Uint8Array;
128
138
  export declare function encodeApplyAttestor(name: string, owners: string[], threshold: number, evidenceHash: string, evidenceUri: string): Uint8Array;
129
139
  /** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
130
140
  export declare function encodeBlockAttestor(): Uint8Array;
141
+ /**
142
+ * Atomic delivery-versus-payment. The sender (initiator) delivers `giveAmount` of `give` to `counterparty`, who delivers
143
+ * `takeAmount` of `take` back, both legs or neither. `"native"` is IUM; `{ asset }` is an RWA asset or a token ref.
144
+ */
145
+ export type DvpOrder = {
146
+ give: SpendAsset;
147
+ giveAmount: bigint;
148
+ counterparty: string;
149
+ take: SpendAsset;
150
+ takeAmount: bigint;
151
+ expiresAt: bigint;
152
+ };
153
+ /** `DvpTerms.domain`: no `ActionPayload` can start with it, so a consent never doubles as an ordinary action signature. */
154
+ export declare const DVP_DOMAIN = 4294967295;
155
+ /**
156
+ * What the counterparty signs, mirroring `dvp_consent(…).signing_bytes`: the order, bound to the genesis hash and the
157
+ * counterparty's current nonce, which settling consumes. Sending any other action first cancels the consent. A multisig
158
+ * counterparty's members each sign these bytes and combine them with `multisigSignature`.
159
+ */
160
+ export declare function dvpConsentBytes(genesis: Uint8Array, initiator: string, order: DvpOrder, counterpartyNonce: number | bigint): Uint8Array;
161
+ export declare function signDvpConsent(privateKey: CryptoKey, genesis: Uint8Array, initiator: string, order: DvpOrder, counterpartyNonce: number | bigint): Promise<string>;
162
+ export declare function encodeSettleDvp(order: DvpOrder, counterpartySignature: string): Uint8Array;
131
163
  /** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
132
164
  export declare const GOVERNANCE_ACTION: {
133
165
  readonly addAttestor: 4;
package/dist/actions.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MULTISIG_TAG, decodeAddress, encodeAddress } from "./bech32.js";
2
2
  import { Reader, Writer, asBuffer, fromHex, toHex } from "./bincode.js";
3
- import { ACCOUNT_POLICY_ACTION, POLICY_VARIANT, encodeAccountPolicy, readAccountPolicy } from "./policy.js";
3
+ import { ACCOUNT_POLICY_ACTION, POLICY_VARIANT, encodeAccountPolicy, readAccountPolicy, readSpendAsset, writeSpendAsset } from "./policy.js";
4
4
  /** These indices are ActionPayload's positional bincode discriminants. Never derive them. */
5
5
  export const ACTION_VARIANT = {
6
6
  transfer: 0, joinValidator: 1, leaveValidator: 2, stake: 3, unstake: 4, registerBlsKey: 6,
@@ -12,13 +12,18 @@ export const ACTION_VARIANT = {
12
12
  snapshotHolders: 33, distributeToHolders: 34, redeemHolders: 35, splitAsset: 36,
13
13
  verifyClaimProof: 37, setPrivateClaims: 38,
14
14
  submitProposal: 30, voteProposal: 31, executeProposal: 32,
15
- applyAttestor: 41, blockAttestor: 42,
15
+ applyAttestor: 41, blockAttestor: 42, settleDvp: 43,
16
16
  };
17
17
  /** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
18
18
  export const TOKEN_ACTION = 39;
19
19
  export const TOKEN_VARIANT = { createToken: 0, mintToken: 1, transferToken: 2, burnToken: 3, renounceMint: 4 };
20
20
  const CLASS = { other: 0, real_estate: 1, equity: 2, bond: 3, stablecoin: 4, commodity: 5, token: 6 };
21
21
  const TOPIC = { kyc: 0, aml: 1, accredited: 2, jurisdiction: 3 };
22
+ /** `IssuerControls` field order — positional bincode, append only. Fixed for the asset's life once registered. */
23
+ export const ISSUER_CONTROLS = ["freeze_asset", "freeze_holder", "lock_holder_amount", "forced_transfer", "recover_holder", "redeem", "split", "set_limits", "set_metadata_uri"];
24
+ /** Every power on, the chain's default (what assets had before controls were selectable). */
25
+ export const ALL_ISSUER_CONTROLS = Object.fromEntries(ISSUER_CONTROLS.map((c) => [c, true]));
26
+ /** @deprecated The flat floor only. The node charges `action_fee + weight × weight_fee`: use `actionFee(await rpc.feeParams(), …)`. */
22
27
  export const ACTION_FEE = 1000000n;
23
28
  export function encodeTransfer(to, amount) { return new Writer().varint(ACTION_VARIANT.transfer).string(to).varint(amount).bytes(); }
24
29
  export function encodeJoinValidator(validator, stake, blsPubkey, blsPop) { return new Writer().varint(ACTION_VARIANT.joinValidator).string(validator).varint(stake).vec(Array.from(blsPubkey), (w, b) => w.u8(b)).vec(Array.from(blsPop), (w, b) => w.u8(b)).bytes(); }
@@ -30,7 +35,8 @@ export function encodeAuthorizeOperator(operator) { return new Writer().varint(A
30
35
  export function encodeRevokeOperator() { return new Writer().varint(ACTION_VARIANT.revokeOperator).bytes(); }
31
36
  export function encodeGrantAttestation(subject, hash, topics, jurisdiction) { return new Writer().varint(ACTION_VARIANT.grantAttestation).string(subject).string(hash).vec(topics, (w, topic) => w.varint(TOPIC[topic])).option(jurisdiction, (w, value) => w.string(value)).bytes(); }
32
37
  export function encodeRevokeAttestation(subject) { return new Writer().varint(ACTION_VARIANT.revokeAttestation).string(subject).bytes(); }
33
- export function encodeRegisterAsset(assetId, complianceRequired, metadata) { return new Writer().varint(ACTION_VARIANT.registerAsset).string(assetId).bool(complianceRequired).varint(CLASS[metadata.asset_class]).u8(metadata.decimals).vec(metadata.required_claims, (w, topic) => w.varint(TOPIC[topic])).option(metadata.allowed_jurisdictions, (w, codes) => w.vec(codes, (ww, code) => ww.string(code))).option(metadata.max_supply, (w, cap) => w.varint(BigInt(cap))).option(metadata.metadata_uri, (w, uri) => w.string(uri)).string(metadata.symbol).string(metadata.name).bytes(); }
38
+ export function encodeRegisterAsset(assetId, complianceRequired, metadata) { const w = new Writer().varint(ACTION_VARIANT.registerAsset).string(assetId).bool(complianceRequired).varint(CLASS[metadata.asset_class]).u8(metadata.decimals).vec(metadata.required_claims, (w, topic) => w.varint(TOPIC[topic])).option(metadata.allowed_jurisdictions, (w, codes) => w.vec(codes, (ww, code) => ww.string(code))).option(metadata.max_supply, (w, cap) => w.varint(BigInt(cap))).option(metadata.metadata_uri, (w, uri) => w.string(uri)).string(metadata.symbol).string(metadata.name); const controls = metadata.controls ?? ALL_ISSUER_CONTROLS; for (const c of ISSUER_CONTROLS)
39
+ w.bool(controls[c]); return w.bytes(); }
34
40
  export function encodeIssueAsset(asset, amount) { return new Writer().varint(ACTION_VARIANT.issueAsset).string(asset).varint(amount).bytes(); }
35
41
  export function encodeTransferAsset(asset, to, amount) { return new Writer().varint(ACTION_VARIANT.transferAsset).string(asset).string(to).varint(amount).bytes(); }
36
42
  export function encodeFreezeAsset(asset, frozen, reason) { return new Writer().varint(frozen ? ACTION_VARIANT.freezeAsset : ACTION_VARIANT.unfreezeAsset).string(asset).string(reason).bytes(); }
@@ -73,6 +79,30 @@ export function encodeRenounceMint(tokenRef) { return token("renounceMint").stri
73
79
  export function encodeApplyAttestor(name, owners, threshold, evidenceHash, evidenceUri) { return new Writer().varint(ACTION_VARIANT.applyAttestor).string(name).vec(owners, (w, o) => w.string(o)).u8(threshold).string(evidenceHash).string(evidenceUri).bytes(); }
74
80
  /** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
75
81
  export function encodeBlockAttestor() { return new Writer().varint(ACTION_VARIANT.blockAttestor).bytes(); }
82
+ /** `DvpTerms.domain`: no `ActionPayload` can start with it, so a consent never doubles as an ordinary action signature. */
83
+ export const DVP_DOMAIN = 0xffff_ffff;
84
+ /**
85
+ * What the counterparty signs, mirroring `dvp_consent(…).signing_bytes`: the order, bound to the genesis hash and the
86
+ * counterparty's current nonce, which settling consumes. Sending any other action first cancels the consent. A multisig
87
+ * counterparty's members each sign these bytes and combine them with `multisigSignature`.
88
+ */
89
+ export function dvpConsentBytes(genesis, initiator, order, counterpartyNonce) {
90
+ const w = new Writer().varint(DVP_DOMAIN).string(initiator);
91
+ writeSpendAsset(w, order.give);
92
+ w.varint(order.giveAmount);
93
+ writeSpendAsset(w, order.take);
94
+ w.varint(order.takeAmount).varint(order.expiresAt);
95
+ return signingBytes(genesis, order.counterparty, counterpartyNonce, w.bytes());
96
+ }
97
+ export async function signDvpConsent(privateKey, genesis, initiator, order, counterpartyNonce) { return toHex(new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, asBuffer(dvpConsentBytes(genesis, initiator, order, counterpartyNonce))))); }
98
+ export function encodeSettleDvp(order, counterpartySignature) {
99
+ const w = new Writer().varint(ACTION_VARIANT.settleDvp);
100
+ writeSpendAsset(w, order.give);
101
+ w.varint(order.giveAmount).string(order.counterparty);
102
+ writeSpendAsset(w, order.take);
103
+ w.varint(order.takeAmount).varint(order.expiresAt).string(counterpartySignature);
104
+ return w.bytes();
105
+ }
76
106
  /** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
77
107
  export const GOVERNANCE_ACTION = { addAttestor: 4, removeAttestor: 5, unblockAttestor: 6 };
78
108
  /** An active validator opens a vote. `addAttestor` needs the attestor's multisig address and its owners; applicants who are not validators use `encodeApplyAttestor`. A removal blocks that attestor's new grants while it runs. */
@@ -130,6 +160,7 @@ export function encodePayload({ name, input }) {
130
160
  case "executeProposal": return encodeExecuteProposal(BigInt(input.proposal));
131
161
  case "applyAttestor": return encodeApplyAttestor(input.name, input.owners, input.threshold, input.evidenceHash, input.evidenceUri);
132
162
  case "blockAttestor": return encodeBlockAttestor();
163
+ case "settleDvp": return encodeSettleDvp({ give: input.give, giveAmount: BigInt(input.giveAmount), counterparty: input.counterparty, take: input.take, takeAmount: BigInt(input.takeAmount), expiresAt: BigInt(input.expiresAt) }, input.counterpartySignature);
133
164
  case "createToken": return encodeCreateToken(input.symbol, input.name, input.decimals, BigInt(input.initialSupply), nullableBig(input.maxSupply), input.mintable);
134
165
  case "mintToken": return encodeMintToken(input.token, input.to, BigInt(input.amount));
135
166
  case "transferToken": return encodeTransferToken(input.token, input.to, BigInt(input.amount));
@@ -168,7 +199,7 @@ export function decodePayload(payload) {
168
199
  revokeOperator: () => ({}),
169
200
  grantAttestation: () => ({ subject: str(), hash: str(), topics: topics(), jurisdiction: r.option(str) }),
170
201
  revokeAttestation: () => ({ subject: str() }),
171
- registerAsset: () => ({ assetId: str(), complianceRequired: r.bool(), metadata: { asset_class: known(CLASS_NAME[Number(r.varint())], "asset class"), decimals: r.u8(), required_claims: topics(), allowed_jurisdictions: r.option((rr) => rr.vec(str)), max_supply: r.option(amount), metadata_uri: r.option(str), symbol: str(), name: str() } }),
202
+ registerAsset: () => ({ assetId: str(), complianceRequired: r.bool(), metadata: { asset_class: known(CLASS_NAME[Number(r.varint())], "asset class"), decimals: r.u8(), required_claims: topics(), allowed_jurisdictions: r.option((rr) => rr.vec(str)), max_supply: r.option(amount), metadata_uri: r.option(str), symbol: str(), name: str(), controls: Object.fromEntries(ISSUER_CONTROLS.map((c) => [c, r.bool()])) } }),
172
203
  issueAsset: () => ({ asset: str(), amount: amount() }),
173
204
  transferAsset: () => ({ asset: str(), to: str(), amount: amount() }),
174
205
  freezeAsset: () => ({ asset: str(), frozen: true, reason: str() }),
@@ -196,6 +227,7 @@ export function decodePayload(payload) {
196
227
  executeProposal: () => ({ proposal: amount() }),
197
228
  applyAttestor: () => ({ name: str(), owners: r.vec((rr) => rr.string()), threshold: r.u8(), evidenceHash: str(), evidenceUri: str() }),
198
229
  blockAttestor: () => ({}),
230
+ settleDvp: () => ({ give: readSpendAsset(r), giveAmount: amount(), counterparty: str(), take: readSpendAsset(r), takeAmount: amount(), expiresAt: amount(), counterpartySignature: str() }),
199
231
  createToken: () => ({ symbol: str(), name: str(), decimals: r.u8(), initialSupply: amount(), maxSupply: r.option(amount), mintable: r.bool() }),
200
232
  mintToken: () => ({ token: str(), to: str(), amount: amount() }),
201
233
  transferToken: () => ({ token: str(), to: str(), amount: amount() }),
package/dist/cli.js CHANGED
@@ -3,10 +3,10 @@ import { readFile, writeFile } from "node:fs/promises";
3
3
  import { basename } from "node:path";
4
4
  import { stdin as input, stderr, stdout } from "node:process";
5
5
  import { createPrivateKey, createPublicKey } from "node:crypto";
6
- import { ArxiumRpc, RpcError, encodeApplyAttestor, encodeBlockAttestor, encodeExecuteProposal, encodeSubmitProposal, encodeVoteProposal, encodeAuthorizeOperator, encodeGrantAttestation, encodeIssueAsset, encodeRegisterAsset, encodeTransferAsset, encodeJoinValidator, encodeLeaveValidator, encodeRegisterBlsKey, encodeRevokeAttestation, encodeRevokeOperator, encodeStake, encodeTransfer, encodeUnstake, generateKey, isKeyFile, signAction, submitBody, unlockKey, verifyKeyFile, verifySignedAction, wrapPkcs8, encodeAddress, toHex, toKeyFile, PKCS8_ED25519_PREFIX, assemblePolicySignature, decodePayload, encodePayload, fromHex, multisigAddress, multisigSignature, signingBytes, asBuffer, } from "./index.js";
6
+ import { ArxiumRpc, RpcError, encodeApplyAttestor, encodeBlockAttestor, encodeExecuteProposal, encodeSubmitProposal, encodeVoteProposal, encodeAuthorizeOperator, encodeGrantAttestation, encodeIssueAsset, encodeRegisterAsset, encodeFreezeAsset, encodeSetHolderFrozen, encodeSetAssetLimits, encodeTransferAsset, encodeJoinValidator, encodeLeaveValidator, encodeRegisterBlsKey, encodeRevokeAttestation, encodeRevokeOperator, encodeStake, encodeTransfer, encodeUnstake, generateKey, isKeyFile, signAction, submitBody, unlockKey, verifyKeyFile, verifySignedAction, wrapPkcs8, encodeAddress, toHex, toKeyFile, PKCS8_ED25519_PREFIX, assemblePolicySignature, decodePayload, encodePayload, fromHex, multisigAddress, multisigSignature, signingBytes, asBuffer, ALL_ISSUER_CONTROLS, ISSUER_CONTROLS, bigintReplacer, encodeBurnAsset, encodeIssuerForcedTransfer, encodeRecoverHolder, encodeIssueAssetTo, encodeLockHolderAmount, encodeLockHolderAmountUntil, encodeLockIssuance, encodeTransferIssuer, encodeSetAssetMetadataUri, encodeSetPrivateClaims, encodeSnapshotHolders, encodeDistributeToHolders, encodeRedeemHolders, encodeSplitAsset, encodeCreateToken, encodeMintToken, encodeTransferToken, encodeBurnToken, encodeRenounceMint, encodeSettleDvp, signDvpConsent, } from "./index.js";
7
7
  class UsageError extends Error {
8
8
  }
9
- function output(value, json) { stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : typeof value === "string" ? `${value}\n` : `${JSON.stringify(value, null, 2)}\n`); }
9
+ function output(value, json) { stdout.write(!json && typeof value === "string" ? `${value}\n` : `${JSON.stringify(value, bigintReplacer, 2)}\n`); }
10
10
  function parseGlobals(args) { let rpc = process.env.ARX_RPC ?? "http://127.0.0.1:8080", token = process.env.ARX_TOKEN, key = process.env.ARX_KEY, json = false, nonce; const rest = []; for (let i = 0; i < args.length; i++) {
11
11
  const arg = args[i];
12
12
  if (arg === "--rpc")
@@ -95,13 +95,80 @@ function payload(action, args) { const topics = (list) => list ? list.split(",")
95
95
  case "register-asset": {
96
96
  const [assetId, symbol, name, decimals] = args;
97
97
  if (!assetId || !symbol || !name || !/^\d+$/.test(decimals ?? ""))
98
- throwUsage("register-asset needs <asset-id> <symbol> <name> <decimals> [topics] [jurisdictions]");
99
- return encodeRegisterAsset(assetId, false, { asset_class: "other", decimals: Number(decimals), required_claims: topics(args[4]), allowed_jurisdictions: args[5] ? args[5].split(",") : null, max_supply: null, metadata_uri: null, symbol, name });
98
+ throwUsage("register-asset needs <asset-id> <symbol> <name> <decimals> [topics|-] [jurisdictions|-] [controls: all|none|freeze_asset,forced_transfer,...]");
99
+ return encodeRegisterAsset(assetId, false, { asset_class: "other", decimals: Number(decimals), required_claims: args[4] === "-" ? [] : topics(args[4]), allowed_jurisdictions: args[5] && args[5] !== "-" ? args[5].split(",") : null, max_supply: null, metadata_uri: null, symbol, name, controls: controlsArg(args[6]) });
100
100
  }
101
101
  case "issue-asset": return encodeIssueAsset(args[0] ?? throwUsage("issue-asset needs <asset-ref> <amount>"), amount(1));
102
102
  case "transfer-asset": return encodeTransferAsset(args[0] ?? throwUsage("transfer-asset needs <asset-ref> <to> <amount>"), args[1] ?? throwUsage("transfer-asset needs <asset-ref> <to> <amount>"), amount(2));
103
- default: throwUsage(`unsupported action: ${action}`);
103
+ case "set-asset-limits": {
104
+ const asset = args[0] ?? throwUsage("set-asset-limits needs <asset-ref> <max-holders|-> [max-balance-per-holder|-] [max-attestation-age-blocks|-]");
105
+ const opt = (index) => { const value = args[index]; if (value === undefined || value === "-")
106
+ return null; if (!/^\d+$/.test(value))
107
+ throwUsage("limits must be integers, or - for no limit"); return BigInt(value); };
108
+ const holders = opt(1);
109
+ return encodeSetAssetLimits(asset, holders === null ? null : Number(holders), opt(2), opt(3));
110
+ }
111
+ case "freeze-asset":
112
+ case "unfreeze-asset": return encodeFreezeAsset(args[0] ?? throwUsage(`${action} needs <asset-ref> <reason>`), action === "freeze-asset", args[1] ?? throwUsage(`${action} needs <asset-ref> <reason>`));
113
+ case "freeze-holder":
114
+ case "unfreeze-holder": return encodeSetHolderFrozen(args[0] ?? throwUsage(`${action} needs <asset-ref> <holder>`), args[1] ?? throwUsage(`${action} needs <asset-ref> <holder>`), action === "freeze-holder");
115
+ default: return morePayload(action, args);
104
116
  } }
117
+ /** Issuer, corporate and token actions. `<…>` arguments are required; amounts and heights are raw integers. */
118
+ const USAGE = {
119
+ "burn-asset": "<asset-ref> <amount>", "forced-transfer": "<asset-ref> <from> <to> <amount> <reason>", "recover-holder": "<asset-ref> <lost> <replacement>",
120
+ "issue-asset-to": "<asset-ref> <to> <amount>", "lock-holder-amount": "<asset-ref> <holder> <amount> [until-height]", "unlock-holder-amount": "<asset-ref> <holder> <amount>",
121
+ "lock-issuance": "<asset-ref>", "transfer-issuer": "<asset-ref> <new-issuer>", "set-metadata-uri": "<asset-ref> <uri|->", "set-private-claims": "<asset-ref> on|off",
122
+ "snapshot-holders": "<asset-ref>", "distribute": "<asset-ref> <snapshot-height> <payout-asset-ref> <total>", "redeem-holders": "<asset-ref> <snapshot-height>",
123
+ "split-asset": "<asset-ref> <snapshot-height> <numerator> <denominator>",
124
+ "create-token": "<symbol> <name> <decimals> <initial-supply> <max-supply|-> mintable|fixed", "mint-token": "<token-ref> <to> <amount>",
125
+ "transfer-token": "<token-ref> <to> <amount>", "burn-token": "<token-ref> <amount>", "renounce-mint": "<token-ref>",
126
+ "settle-dvp": "<give-asset-ref|native> <give-amount> <counterparty> <take-asset-ref|native> <take-amount> <expires-at-height> <counterparty-signature>",
127
+ };
128
+ const spendArg = (value) => value === "native" ? "native" : { asset: value };
129
+ function morePayload(action, args) {
130
+ const usage = USAGE[action] ?? throwUsage(`unsupported action: ${action}`);
131
+ const arg = (i) => args[i] ?? throwUsage(`${action} needs ${usage}`);
132
+ const int = (i) => { const value = arg(i); if (!/^\d+$/.test(value))
133
+ throwUsage(`${action} needs ${usage} (integers in base units)`); return BigInt(value); };
134
+ switch (action) {
135
+ case "burn-asset": return encodeBurnAsset(arg(0), int(1));
136
+ case "forced-transfer": return encodeIssuerForcedTransfer(arg(0), arg(1), arg(2), int(3), arg(4));
137
+ case "recover-holder": return encodeRecoverHolder(arg(0), arg(1), arg(2));
138
+ case "issue-asset-to": return encodeIssueAssetTo(arg(0), arg(1), int(2));
139
+ case "lock-holder-amount": return args[3] === undefined ? encodeLockHolderAmount(arg(0), arg(1), int(2), true) : encodeLockHolderAmountUntil(arg(0), arg(1), int(2), int(3));
140
+ case "unlock-holder-amount": return encodeLockHolderAmount(arg(0), arg(1), int(2), false);
141
+ case "lock-issuance": return encodeLockIssuance(arg(0));
142
+ case "transfer-issuer": return encodeTransferIssuer(arg(0), arg(1));
143
+ case "set-metadata-uri": return encodeSetAssetMetadataUri(arg(0), arg(1) === "-" ? null : arg(1));
144
+ case "set-private-claims": {
145
+ const on = arg(1);
146
+ if (on !== "on" && on !== "off")
147
+ throwUsage(`${action} needs ${usage}`);
148
+ return encodeSetPrivateClaims(arg(0), on === "on");
149
+ }
150
+ case "snapshot-holders": return encodeSnapshotHolders(arg(0));
151
+ case "distribute": return encodeDistributeToHolders(arg(0), int(1), arg(2), int(3));
152
+ case "redeem-holders": return encodeRedeemHolders(arg(0), int(1));
153
+ case "split-asset": return encodeSplitAsset(arg(0), int(1), int(2), int(3));
154
+ case "create-token": {
155
+ const kind = arg(5);
156
+ if (kind !== "mintable" && kind !== "fixed")
157
+ throwUsage(`${action} needs ${usage}`);
158
+ return encodeCreateToken(arg(0), arg(1), Number(int(2)), int(3), arg(4) === "-" ? null : int(4), kind === "mintable");
159
+ }
160
+ case "mint-token": return encodeMintToken(arg(0), arg(1), int(2));
161
+ case "transfer-token": return encodeTransferToken(arg(0), arg(1), int(2));
162
+ case "burn-token": return encodeBurnToken(arg(0), int(1));
163
+ case "settle-dvp": return encodeSettleDvp({ give: spendArg(arg(0)), giveAmount: int(1), counterparty: arg(2), take: spendArg(arg(3)), takeAmount: int(4), expiresAt: int(5) }, arg(6));
164
+ default: return encodeRenounceMint(arg(0));
165
+ }
166
+ }
167
+ /** Enabled issuer controls, fixed for the asset's life. Omitted or "all" keeps every power (the node's default). */
168
+ function controlsArg(value) { if (!value || value === "all")
169
+ return ALL_ISSUER_CONTROLS; const on = value === "none" ? [] : value.split(","); for (const c of on)
170
+ if (!ISSUER_CONTROLS.includes(c))
171
+ throwUsage(`unknown control: ${c} (${ISSUER_CONTROLS.join(", ")})`); return Object.fromEntries(ISSUER_CONTROLS.map((c) => [c, on.includes(c)])); }
105
172
  function hexArg(value) { if (!value || !/^[0-9a-f]*$/i.test(value) || value.length % 2)
106
173
  throwUsage("expected even-length hex"); return Uint8Array.from(value.match(/../g)?.map((byte) => Number.parseInt(byte, 16)) ?? []); }
107
174
  async function main() {
@@ -113,7 +180,7 @@ async function main() {
113
180
  return;
114
181
  }
115
182
  if (!command)
116
- throwUsage("use: arx keys|sign|submit|send|query|verify");
183
+ throwUsage("use: arx keys|sign|submit|send|dvp-consent|query|verify");
117
184
  if (command === "keys") {
118
185
  const [operation, ...keyArgs] = rest;
119
186
  if (operation === "new") {
@@ -220,6 +287,19 @@ async function main() {
220
287
  }
221
288
  throwUsage("use: arx multisig address|build|sign|combine|link");
222
289
  }
290
+ // The counterparty's half of a DvP: sign the order at its current nonce. The initiator sends it with `arx send settle-dvp`.
291
+ if (command === "dvp-consent") {
292
+ const use = "dvp-consent needs <initiator> <give-asset-ref|native> <give-amount> <take-asset-ref|native> <take-amount> <expires-at-height>", [initiator, give, giveAmount, take, takeAmount, expiresAt] = rest;
293
+ if (!initiator || !give || !take || ![giveAmount, takeAmount, expiresAt].every((v) => /^\d+$/.test(v ?? "")))
294
+ throwUsage(use);
295
+ const { file, privateKey } = await loadKey(globals.key);
296
+ const nonce = globals.nonce ?? await rpc.account(file.address).then((account) => account.nonce, (error) => { if (error instanceof RpcError && error.status === 404)
297
+ return 0; throw error; });
298
+ const order = { give: spendArg(give), giveAmount: BigInt(giveAmount), counterparty: file.address, take: spendArg(take), takeAmount: BigInt(takeAmount), expiresAt: BigInt(expiresAt) };
299
+ const signature = await signDvpConsent(privateKey, await rpc.genesisHash(), initiator, order, nonce);
300
+ output({ counterparty: file.address, counterpartyNonce: nonce, signature, send: ["settle-dvp", give, giveAmount, file.address, take, takeAmount, expiresAt, signature] }, true);
301
+ return;
302
+ }
223
303
  if (command === "sign" || command === "send") {
224
304
  const action = rest[0] ?? throwUsage(`${command} needs an action`), { file, privateKey } = await loadKey(globals.key), encoded = payload(action, rest.slice(1));
225
305
  if (command === "sign") {
@@ -255,7 +335,17 @@ async function main() {
255
335
  }
256
336
  if (command === "query") {
257
337
  const [operation, value] = rest;
258
- const result = operation === "status" ? await rpc.status() : operation === "account" ? await rpc.account(value ?? throwUsage("account needs an address")) : operation === "block" ? (/^\d+$/.test(value ?? "") ? await rpc.block(Number(value)) : await rpc.blockByHash(value ?? throwUsage("block needs a height or hash"))) : operation === "action" ? await rpc.action(value ?? throwUsage("action needs a signature")) : operation === "validators" ? await rpc.validators() : operation === "finality" ? await rpc.finality() : operation === "search" ? await rpc.search(value ?? throwUsage("search needs a query")) : throwUsage("use: arx query status|account|block|action|validators|finality|search");
338
+ const result = operation === "status" ? await rpc.status() : operation === "account" ? await rpc.account(value ?? throwUsage("account needs an address")) : operation === "block" ? (/^\d+$/.test(value ?? "") ? await rpc.block(Number(value)) : await rpc.blockByHash(value ?? throwUsage("block needs a height or hash"))) : operation === "action" ? await rpc.action(value ?? throwUsage("action needs a signature")) : operation === "validators" ? await rpc.validators() : operation === "finality" ? await rpc.finality() : operation === "search" ? await rpc.search(value ?? throwUsage("search needs a query"))
339
+ : operation === "assets" ? (value ? await rpc.assets(value) : await rpc.listAssets())
340
+ : operation === "asset" ? await rpc.asset(value ?? throwUsage("asset needs an asset ref"))
341
+ : operation === "holders" ? await rpc.assetHolders(value ?? throwUsage("holders needs an asset ref"))
342
+ : operation === "balance" ? await rpc.assetBalance(value ?? throwUsage("balance needs <address> <asset-ref>"), rest[2] ?? throwUsage("balance needs <address> <asset-ref>"))
343
+ : operation === "attestors" ? await rpc.attestors()
344
+ : operation === "attestor" ? await rpc.attestor(value ?? throwUsage("attestor needs an address"))
345
+ : operation === "validator" ? await rpc.validator(value ?? throwUsage("validator needs an address"))
346
+ : operation === "stakes" ? await rpc.stakes(value ?? throwUsage("stakes needs an address"))
347
+ : operation === "fee" ? await rpc.actionFee()
348
+ : throwUsage("use: arx query status|account|block|action|validators|validator|finality|search|assets [address]|asset|holders|balance|attestors|attestor|stakes|fee");
259
349
  output(result, globals.json);
260
350
  return;
261
351
  }
package/dist/fees.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /** `action_fee` and `weight_fee` from `GET /action-fee` (`rpc.feeParams()`). */
2
+ export type FeeParams = {
3
+ actionFee: bigint;
4
+ weightFee: bigint;
5
+ };
6
+ /** `POLICY_AUTHORIZATION_WEIGHT`: added when a stateful account policy authorizes the action. */
7
+ export declare const POLICY_AUTHORIZATION_WEIGHT = 5000;
8
+ /** A single-key ed25519 signature: 64 bytes as hex. */
9
+ export declare const SINGLE_SIGNATURE_LENGTH = 128;
10
+ /** Hex length of a `threshold`-of-`members` multisig witness (`multisigSignature`), for estimating a fee before anyone signs. */
11
+ export declare const multisigSignatureLength: (threshold: number, members: number) => number;
12
+ /**
13
+ * Weight that the node charges for an action. Pass the real `signature` once it is signed. Before signing, pass
14
+ * `signatureLength` instead (128 for one key, longer for a multisig witness), and set `policy` when a stateful account policy will sign.
15
+ */
16
+ export declare function actionWeight(sender: string, nonce: number | bigint, payload: Uint8Array, options?: {
17
+ signature?: string;
18
+ signatureLength?: number;
19
+ policy?: boolean;
20
+ }): number;
21
+ /** The fee the node charges, in IUM. `CreateToken` also pays `token_create_fee` on top (see `rpc.feeParams()`). */
22
+ export declare function actionFee(params: FeeParams, sender: string, nonce: number | bigint, payload: Uint8Array, options?: {
23
+ signature?: string;
24
+ signatureLength?: number;
25
+ policy?: boolean;
26
+ }): bigint;
package/dist/fees.js ADDED
@@ -0,0 +1,63 @@
1
+ import { ACTION_VARIANT, TOKEN_ACTION, TOKEN_VARIANT } from "./actions.js";
2
+ import { Reader } from "./bincode.js";
3
+ import { ACCOUNT_POLICY_ACTION } from "./policy.js";
4
+ /** `base_weight` in metering.rs, by `ActionPayload` discriminant. Token and account-policy actions are handled below. */
5
+ const BASE_WEIGHT = {
6
+ [ACTION_VARIANT.transfer]: 50,
7
+ [ACTION_VARIANT.stake]: 100, [ACTION_VARIANT.unstake]: 100,
8
+ [ACTION_VARIANT.joinValidator]: 1_500, [ACTION_VARIANT.registerBlsKey]: 1_500,
9
+ [ACTION_VARIANT.leaveValidator]: 150,
10
+ [ACTION_VARIANT.authorizeOperator]: 100, [ACTION_VARIANT.revokeOperator]: 100,
11
+ 7 /* VerifyIdentityCredential */: 5_000, [ACTION_VARIANT.verifyClaimProof]: 5_000,
12
+ [ACTION_VARIANT.grantAttestation]: 100, [ACTION_VARIANT.revokeAttestation]: 100,
13
+ [ACTION_VARIANT.applyAttestor]: 150, [ACTION_VARIANT.blockAttestor]: 150,
14
+ [ACTION_VARIANT.registerAsset]: 150,
15
+ [ACTION_VARIANT.issueAsset]: 150, [ACTION_VARIANT.issueAssetTo]: 150, [ACTION_VARIANT.transferAsset]: 150,
16
+ [ACTION_VARIANT.issuerForcedTransfer]: 150, [ACTION_VARIANT.burnAsset]: 150, [ACTION_VARIANT.recoverHolder]: 150,
17
+ [ACTION_VARIANT.freezeAsset]: 100, [ACTION_VARIANT.unfreezeAsset]: 100, [ACTION_VARIANT.setHolderFrozen]: 100,
18
+ [ACTION_VARIANT.lockHolderAmount]: 100, [ACTION_VARIANT.lockHolderAmountUntil]: 100, [ACTION_VARIANT.unlockHolderAmount]: 100,
19
+ [ACTION_VARIANT.setAssetLimits]: 100, [ACTION_VARIANT.setPrivateClaims]: 100, [ACTION_VARIANT.lockIssuance]: 100,
20
+ [ACTION_VARIANT.transferIssuer]: 100, [ACTION_VARIANT.setAssetMetadataUri]: 100,
21
+ [ACTION_VARIANT.snapshotHolders]: 2_000, [ACTION_VARIANT.distributeToHolders]: 15_000,
22
+ [ACTION_VARIANT.redeemHolders]: 10_000, [ACTION_VARIANT.splitAsset]: 10_000,
23
+ [ACTION_VARIANT.settleDvp]: 350,
24
+ [ACTION_VARIANT.submitProposal]: 150, [ACTION_VARIANT.voteProposal]: 150, [ACTION_VARIANT.executeProposal]: 150,
25
+ 5 /* SubmitEquivocationEvidence */: 2_000,
26
+ 15 /* SubmitExecutionFault */: 500_000,
27
+ };
28
+ /** `POLICY_AUTHORIZATION_WEIGHT`: added when a stateful account policy authorizes the action. */
29
+ export const POLICY_AUTHORIZATION_WEIGHT = 5_000;
30
+ /** A single-key ed25519 signature: 64 bytes as hex. */
31
+ export const SINGLE_SIGNATURE_LENGTH = 128;
32
+ /** Hex length of a `threshold`-of-`members` multisig witness (`multisigSignature`), for estimating a fee before anyone signs. */
33
+ export const multisigSignatureLength = (threshold, members) => 2 * (2 + 32 * members + 65 * threshold);
34
+ function baseWeight(payload) {
35
+ const r = new Reader(payload), variant = Number(r.varint());
36
+ if (variant === TOKEN_ACTION)
37
+ return Number(r.varint()) === TOKEN_VARIANT.createToken ? 150 : 100;
38
+ if (variant === ACCOUNT_POLICY_ACTION)
39
+ return 1_000;
40
+ const weight = BASE_WEIGHT[variant];
41
+ if (weight === undefined)
42
+ throw new Error(`unknown action variant: ${variant}`);
43
+ return weight;
44
+ }
45
+ /** bincode `standard()` varint length. */
46
+ const varintLength = (n) => (n <= 250 ? 1 : n <= 0xffff ? 3 : n <= 0xffff_ffff ? 5 : 9);
47
+ const isPolicyWitness = (signature) => signature.length > 128 && /^a7(00|01|02)/i.test(signature);
48
+ /**
49
+ * Weight that the node charges for an action. Pass the real `signature` once it is signed. Before signing, pass
50
+ * `signatureLength` instead (128 for one key, longer for a multisig witness), and set `policy` when a stateful account policy will sign.
51
+ */
52
+ export function actionWeight(sender, nonce, payload, options = {}) {
53
+ const sigLength = options.signature?.length ?? options.signatureLength ?? SINGLE_SIGNATURE_LENGTH;
54
+ const policy = options.signature !== undefined ? isPolicyWitness(options.signature) : !!options.policy;
55
+ const senderLength = new TextEncoder().encode(sender).length;
56
+ // `ActionWire`: sender string, nonce, `Some(signature)`, payload as a length-prefixed byte string.
57
+ const size = varintLength(senderLength) + senderLength + varintLength(Number(nonce)) + 1 + varintLength(sigLength) + sigLength + varintLength(payload.length) + payload.length;
58
+ return baseWeight(payload) + size + (policy ? POLICY_AUTHORIZATION_WEIGHT : 0);
59
+ }
60
+ /** The fee the node charges, in IUM. `CreateToken` also pays `token_create_fee` on top (see `rpc.feeParams()`). */
61
+ export function actionFee(params, sender, nonce, payload, options = {}) {
62
+ return params.actionFee + BigInt(actionWeight(sender, nonce, payload, options)) * params.weightFee;
63
+ }
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export * from "./bincode.js";
5
5
  export * from "./keys.js";
6
6
  export * from "./rpc.js";
7
7
  export * from "./policy.js";
8
+ export * from "./fees.js";
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@ export * from "./bincode.js";
5
5
  export * from "./keys.js";
6
6
  export * from "./rpc.js";
7
7
  export * from "./policy.js";
8
+ export * from "./fees.js";
package/dist/policy.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Reader } from "./bincode.js";
1
+ import { Reader, Writer } from "./bincode.js";
2
2
  export declare const ACCOUNT_POLICY_ACTION = 40;
3
3
  export declare const POLICY_VARIANT: {
4
4
  readonly setAccountPolicy: 0;
@@ -39,7 +39,11 @@ export type AccountPolicy = {
39
39
  } | null;
40
40
  };
41
41
  export type PolicyMode = "owner" | "session" | "guardian";
42
+ declare function asset(w: Writer, a: SpendAsset): void;
43
+ export { asset as writeSpendAsset };
42
44
  export declare function encodeAccountPolicy(name: keyof typeof POLICY_VARIANT, input: Record<string, any>): Uint8Array;
45
+ declare function readAsset(r: Reader): SpendAsset;
46
+ export { readAsset as readSpendAsset };
43
47
  export declare function readAccountPolicy(r: Reader): {
44
48
  name: keyof typeof POLICY_VARIANT;
45
49
  input: Record<string, any>;
package/dist/policy.js CHANGED
@@ -16,6 +16,7 @@ function asset(w, a) { if (a === "native")
16
16
  w.varint(0);
17
17
  else
18
18
  w.varint(1).string(a.asset); }
19
+ export { asset as writeSpendAsset };
19
20
  function recipients(w, list) { if (list.length > 64 || list.some((a, i) => i > 0 && list[i - 1] >= a))
20
21
  throw new Error("recipients must be bounded, unique and sorted"); w.vec(list, (ww, a) => ww.string(a)); }
21
22
  function session(w, s) { w.raw(key(s.publicKey)).varint(bounded(s.expiresAt, 64)); asset(w, s.asset); w.varint(bounded(s.allowance, 128)); recipients(w, s.recipients); }
@@ -56,6 +57,7 @@ function readThreshold(r) { return { threshold: r.u8(), members: r.vec(readKey)
56
57
  function readAsset(r) { const tag = r.varint(); if (tag === 0n)
57
58
  return "native"; if (tag === 1n)
58
59
  return { asset: r.string() }; throw new Error("unknown spend asset"); }
60
+ export { readAsset as readSpendAsset };
59
61
  function readSession(r) { return { publicKey: readKey(r), expiresAt: r.varint().toString(), asset: readAsset(r), allowance: r.varint().toString(), recipients: r.vec(rr => rr.string()) }; }
60
62
  export function readAccountPolicy(r) {
61
63
  const variant = Number(r.varint()), name = Object.keys(POLICY_VARIANT).find(n => POLICY_VARIANT[n] === variant);
package/dist/rpc.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type SignedAction } from "./actions.js";
1
+ import { type AssetClass, type IssuerControls, type SignedAction } from "./actions.js";
2
+ import type { FeeParams } from "./fees.js";
2
3
  import { type AccountPolicy } from "./policy.js";
3
4
  export type RpcOptions = {
4
5
  rpc: string;
@@ -19,10 +20,15 @@ export type ActionStatus = {
19
20
  } | {
20
21
  status: "unknown";
21
22
  };
23
+ /**
24
+ * `privateKey` signs as a single-key sender. For a multisig or programmable-account sender, pass `sign` instead: it gets the
25
+ * exact signing bytes and the nonce, and returns the action's `signature` field (e.g. from `multisigSignature`).
26
+ */
22
27
  export type SendActionOptions = {
23
- privateKey: CryptoKey;
24
28
  sender: string;
25
29
  payload: Uint8Array;
30
+ privateKey?: CryptoKey;
31
+ sign?: (message: Uint8Array, nonce: number) => Promise<string>;
26
32
  pollIntervalMs?: number;
27
33
  timeoutMs?: number;
28
34
  finalized?: boolean;
@@ -31,34 +37,211 @@ export declare class RpcError extends Error {
31
37
  readonly status: number;
32
38
  constructor(status: number, message: string);
33
39
  }
40
+ /** The chain refused the action. `reason` is the node's exact text, e.g. `compliance check failed: arx1… is not KYC'd/allowlisted`. */
41
+ export declare class ActionDroppedError extends Error {
42
+ readonly reason: string;
43
+ readonly action: SignedAction;
44
+ constructor(reason: string, action: SignedAction);
45
+ }
46
+ export type ClaimTopicName = "Kyc" | "Aml" | "Accredited" | "Jurisdiction";
47
+ export type NodeStatus = {
48
+ chain_name: string;
49
+ version: string;
50
+ genesis_hash: string;
51
+ tip_height: number;
52
+ tip_hash: string;
53
+ finalized_height: number | null;
54
+ final_watermark: number;
55
+ settled_height: number;
56
+ block_interval_secs: number | null;
57
+ storage_schema: number;
58
+ min_migratable_schema: number;
59
+ open_dispute_height: number | null;
60
+ open_dispute_header: string | null;
61
+ open_dispute_cause: string | null;
62
+ };
63
+ export type Account = {
64
+ balance: bigint;
65
+ nonce: number;
66
+ identity_hash: string | null;
67
+ zk_identity_verified: boolean;
68
+ attested_by: string | null;
69
+ claims: ClaimTopicName[];
70
+ jurisdiction: string | null;
71
+ attested_at: number | null;
72
+ programmable?: unknown;
73
+ };
74
+ export type StakeAllocation = {
75
+ master: string;
76
+ validator: string;
77
+ active_amount: bigint;
78
+ unbonding: {
79
+ amount: bigint;
80
+ unlock_at_height: number;
81
+ } | null;
82
+ created_at: number;
83
+ updated_at: number;
84
+ };
85
+ /** One asset in `rpc.assets(address)`. `transfer_eligible`/`eligibility_reason` describe the sending side. */
86
+ export type AccountAssetBalance = {
87
+ ref: string;
88
+ asset_id: string;
89
+ symbol: string;
90
+ name: string;
91
+ issuer: string;
92
+ issuer_attested: boolean;
93
+ compliance_required: boolean;
94
+ frozen: boolean;
95
+ holder_frozen: boolean;
96
+ frozen_amount: bigint;
97
+ transfer_eligible: boolean;
98
+ eligibility_reason: string;
99
+ decimals: number;
100
+ balance: bigint;
101
+ };
102
+ export type Asset = {
103
+ ref: string;
104
+ asset_ref: string;
105
+ asset_id: string;
106
+ issuer: string;
107
+ issuer_attested: boolean;
108
+ holders: number;
109
+ compliance_required: boolean;
110
+ asset_class: string;
111
+ decimals: number;
112
+ required_claims: ClaimTopicName[];
113
+ allowed_jurisdictions: string[] | null;
114
+ max_supply: bigint | null;
115
+ total_supply: bigint;
116
+ frozen: boolean;
117
+ metadata_uri: string | null;
118
+ registered_at: number;
119
+ symbol: string;
120
+ name: string;
121
+ issuance_locked: boolean;
122
+ max_holders: number | null;
123
+ max_balance_per_holder: bigint | null;
124
+ max_attestation_age: number | null;
125
+ holder_count: number;
126
+ snapshot: {
127
+ height: number;
128
+ total: bigint;
129
+ holders: [string, number | bigint][];
130
+ } | null;
131
+ private_claims: boolean;
132
+ controls: IssuerControls;
133
+ };
134
+ export type AssetHolder = {
135
+ address: string;
136
+ balance: bigint;
137
+ frozen: boolean;
138
+ frozen_amount: bigint;
139
+ attested_at: number | null;
140
+ };
141
+ export type Attestor = {
142
+ attestor: string;
143
+ name: string;
144
+ registered_at: number;
145
+ owners: string[];
146
+ threshold: number;
147
+ blocked: boolean;
148
+ self_blocked: boolean;
149
+ removal_pending: boolean;
150
+ };
151
+ export type ValidatorStatus = "Active" | "Pending" | "Tombstoned" | {
152
+ Jailed: {
153
+ until_epoch: number;
154
+ };
155
+ } | {
156
+ Leaving: {
157
+ from_epoch: number;
158
+ };
159
+ };
160
+ export type Validator = {
161
+ address: string;
162
+ status: ValidatorStatus;
163
+ voting_power: number;
164
+ bls_registered: boolean;
165
+ operator: string | null;
166
+ };
167
+ export type ActionFeeInfo = {
168
+ action_fee: bigint;
169
+ weight_fee: bigint;
170
+ max_block_weight: number;
171
+ token_create_fee: bigint;
172
+ };
173
+ export type { AssetClass };
174
+ /**
175
+ * `JSON.parse` that never rounds an integer: the node writes u128 amounts as bare JSON numbers, and anything above 2^53
176
+ * (about 9M ARX in IUM) loses digits in a plain parse. Amount keys become `bigint`; other integers stay `number` unless unsafe.
177
+ */
178
+ export declare function parseJson(text: string): unknown;
179
+ /** `JSON.stringify` replacer for `bigint` values (as decimal strings). */
180
+ export declare const bigintReplacer: (_key: string, value: unknown) => unknown;
34
181
  export declare class ArxiumRpc {
35
182
  readonly rpc: string;
36
183
  private readonly token?;
37
184
  private readonly requestFetch;
185
+ /** One in-flight `sendAction` per sender: the node's account nonce doesn't count the mempool, so two concurrent sends would sign the same nonce. */
186
+ private readonly senders;
38
187
  constructor(options: RpcOptions);
39
188
  private request;
40
189
  private genesis?;
41
190
  /** This chain's genesis hash, fetched once per client; every signature binds it. */
42
191
  genesisHash(): Promise<Uint8Array>;
43
- status<T = Record<string, unknown>>(): Promise<T>;
44
- account<T = Record<string, unknown>>(address: string): Promise<T>;
45
- stake<T = Record<string, unknown>>(address: string): Promise<T>;
46
- stakes<T = Record<string, unknown>[]>(address: string): Promise<T>;
192
+ status<T = NodeStatus>(): Promise<T>;
193
+ account<T = Account>(address: string): Promise<T>;
194
+ /** The account's asset balances, each with `transfer_eligible` and `eligibility_reason` for the sending side. Includes rows that went to zero. */
195
+ assets<T = AccountAssetBalance[]>(address: string): Promise<T>;
196
+ /** One asset balance. 404 (`RpcError`) when the asset was never registered; a registered asset held at zero is `balance: 0n`. */
197
+ assetBalance<T = AccountAssetBalance>(address: string, assetRef: string): Promise<T>;
198
+ stake<T = StakeAllocation>(address: string): Promise<T>;
199
+ stakes<T = StakeAllocation[]>(address: string): Promise<T>;
200
+ /** A stake `master` placed on `validator`'s behalf (`stake(address)` is the self-stake case). */
201
+ delegatedStake<T = StakeAllocation>(master: string, validator: string): Promise<T>;
47
202
  blsKey<T = Record<string, unknown>>(address: string): Promise<T>;
203
+ /** Every registered asset, or one issuer's. */
204
+ listAssets<T = Asset[]>(issuer?: string): Promise<T>;
205
+ asset<T = Asset>(assetRef: string): Promise<T>;
206
+ /** The asset `issuer` registered as `assetId`, or `null` when that id is still free for them. */
207
+ assetByAlias<T = Asset>(issuer: string, assetId: string): Promise<T | null>;
208
+ /** The cap table: every address with a non-zero balance, with its freeze state. */
209
+ assetHolders<T = AssetHolder[]>(assetRef: string): Promise<T>;
210
+ attestors<T = Attestor[]>(): Promise<T>;
211
+ attestor<T = Attestor>(address: string): Promise<T>;
48
212
  submit(action: SignedAction): Promise<unknown>;
49
213
  action(signature: string): Promise<ActionStatus>;
50
214
  blocks<T = unknown>(from: number, to: number): Promise<T>;
51
215
  block<T = unknown>(height: number): Promise<T>;
52
216
  blockByHash<T = unknown>(hash: string): Promise<T>;
53
- validators<T = unknown>(): Promise<T>;
217
+ /** State changes a block made (balances, assets, stake), for indexers. */
218
+ blockEffects<T = unknown>(height: number): Promise<T>;
219
+ effects<T = unknown>(from: number, to: number): Promise<T>;
220
+ /** Sorted validator addresses, at the tip or at `height`. */
221
+ validators<T = string[]>(height?: number): Promise<T>;
222
+ validator<T = Validator>(address: string): Promise<T>;
223
+ /** Voting power by validator address, at the tip or at `height`. */
224
+ validatorPower<T = Record<string, number>>(height?: number): Promise<T>;
225
+ operatorValidators<T = string[]>(operator: string): Promise<T>;
54
226
  finality<T = unknown>(): Promise<T>;
227
+ /** Ids of stored equivocation/fault evidence; `evidence(id)` returns one. */
228
+ evidenceList<T = string[]>(): Promise<T>;
229
+ evidence<T = unknown>(id: string): Promise<T>;
230
+ /** Merkle proofs against a finalized state root, for light clients. */
231
+ accountProof<T = unknown>(address: string): Promise<T>;
232
+ assetBalanceProof<T = unknown>(address: string, assetRef: string): Promise<T>;
233
+ validatorProof<T = unknown>(address: string): Promise<T>;
55
234
  search<T = unknown>(query: string): Promise<T>;
56
235
  minStake<T = unknown>(): Promise<T>;
57
- actionFee<T = unknown>(): Promise<T>;
236
+ actionFee<T = ActionFeeInfo>(): Promise<T>;
237
+ /** The chain's current fee parameters, for `actionFee(params, …)`. */
238
+ feeParams(): Promise<FeeParams>;
239
+ /** Signs at the sender's current nonce, submits and polls until confirmed (or finalized). Calls for one sender run one after another. */
58
240
  sendAction(options: SendActionOptions): Promise<{
59
241
  action: SignedAction;
60
242
  status: ActionStatus;
61
243
  }>;
244
+ private sendNow;
62
245
  }
63
246
  /** Stateful witnesses require the policy at the action's pre-state, not today's
64
247
  * policy. This checks cryptography; circuit execution proves limits and scope. */
package/dist/rpc.js CHANGED
@@ -9,21 +9,73 @@ export class RpcError extends Error {
9
9
  this.status = status;
10
10
  }
11
11
  }
12
+ /** The chain refused the action. `reason` is the node's exact text, e.g. `compliance check failed: arx1… is not KYC'd/allowlisted`. */
13
+ export class ActionDroppedError extends Error {
14
+ reason;
15
+ action;
16
+ constructor(reason, action) {
17
+ super(`action dropped: ${reason}`);
18
+ this.reason = reason;
19
+ this.action = action;
20
+ }
21
+ }
22
+ /** Keys whose integer values are token amounts (u128 on the node): always `bigint`, so callers never see a rounded balance. */
23
+ const AMOUNT_KEYS = new Set(["balance", "amount", "stake", "active_amount", "frozen_amount", "total_supply", "max_supply", "max_balance_per_holder", "total", "action_fee", "weight_fee", "token_create_fee", "min_stake"]);
24
+ const MARK = "\u0000int:";
25
+ /**
26
+ * `JSON.parse` that never rounds an integer: the node writes u128 amounts as bare JSON numbers, and anything above 2^53
27
+ * (about 9M ARX in IUM) loses digits in a plain parse. Amount keys become `bigint`; other integers stay `number` unless unsafe.
28
+ */
29
+ export function parseJson(text) {
30
+ const marked = text.replace(/"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g, (token) => token[0] === '"' || /[.eE]/.test(token) ? token : `"\\u0000int:${token}"`);
31
+ return JSON.parse(marked, (key, value) => {
32
+ if (typeof value !== "string" || !value.startsWith(MARK))
33
+ return value;
34
+ const digits = value.slice(MARK.length), number = Number(digits);
35
+ return AMOUNT_KEYS.has(key) || !Number.isSafeInteger(number) ? BigInt(digits) : number;
36
+ });
37
+ }
38
+ /** `JSON.stringify` replacer for `bigint` values (as decimal strings). */
39
+ export const bigintReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
12
40
  export class ArxiumRpc {
13
41
  rpc;
14
42
  token;
15
43
  requestFetch;
44
+ /** One in-flight `sendAction` per sender: the node's account nonce doesn't count the mempool, so two concurrent sends would sign the same nonce. */
45
+ senders = new Map();
16
46
  constructor(options) { this.rpc = options.rpc.replace(/\/+$/, ""); this.token = options.token; this.requestFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init)); } // browsers and Workers throw "Illegal invocation" if fetch is called with `this` = ArxiumRpc
17
47
  async request(path, init) { const response = await this.requestFetch(`${this.rpc}${path}`, { ...init, headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), ...init?.headers } }); const text = await response.text(); if (!response.ok)
18
- throw new RpcError(response.status, text || `${response.status} ${response.statusText}`); return text ? JSON.parse(text) : undefined; }
48
+ throw new RpcError(response.status, text || `${response.status} ${response.statusText}`); return text ? parseJson(text) : undefined; }
19
49
  genesis;
20
50
  /** This chain's genesis hash, fetched once per client; every signature binds it. */
21
51
  genesisHash() { return this.genesis ??= this.request("/genesis-hash").then((r) => fromHex(r.genesis_hash.replace(/^0x/, "")), (e) => { this.genesis = undefined; throw e; }); }
22
52
  status() { return this.request("/status"); }
23
53
  account(address) { return this.request(`/accounts/${encodeURIComponent(address)}`); }
54
+ /** The account's asset balances, each with `transfer_eligible` and `eligibility_reason` for the sending side. Includes rows that went to zero. */
55
+ assets(address) { return this.request(`/accounts/${encodeURIComponent(address)}/assets`); }
56
+ /** One asset balance. 404 (`RpcError`) when the asset was never registered; a registered asset held at zero is `balance: 0n`. */
57
+ assetBalance(address, assetRef) { return this.request(`/accounts/${encodeURIComponent(address)}/assets/${encodeURIComponent(assetRef)}`); }
24
58
  stake(address) { return this.request(`/accounts/${encodeURIComponent(address)}/stake`); }
25
59
  stakes(address) { return this.request(`/accounts/${encodeURIComponent(address)}/stakes`); }
60
+ /** A stake `master` placed on `validator`'s behalf (`stake(address)` is the self-stake case). */
61
+ delegatedStake(master, validator) { return this.request(`/stake/${encodeURIComponent(master)}/${encodeURIComponent(validator)}`); }
26
62
  blsKey(address) { return this.request(`/accounts/${encodeURIComponent(address)}/bls-key`); }
63
+ /** Every registered asset, or one issuer's. */
64
+ listAssets(issuer) { return this.request(issuer ? `/assets?issuer=${encodeURIComponent(issuer)}` : "/assets"); }
65
+ asset(assetRef) { return this.request(`/assets/${encodeURIComponent(assetRef)}`); }
66
+ /** The asset `issuer` registered as `assetId`, or `null` when that id is still free for them. */
67
+ async assetByAlias(issuer, assetId) { try {
68
+ return await this.request(`/assets/alias/${encodeURIComponent(issuer)}/${encodeURIComponent(assetId)}`);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof RpcError && error.status === 404)
72
+ return null;
73
+ throw error;
74
+ } }
75
+ /** The cap table: every address with a non-zero balance, with its freeze state. */
76
+ assetHolders(assetRef) { return this.request(`/assets/${encodeURIComponent(assetRef)}/holders`); }
77
+ attestors() { return this.request("/attestors"); }
78
+ attestor(address) { return this.request(`/attestors/${encodeURIComponent(address)}`); }
27
79
  submit(action) { return this.request("/actions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(action) }); }
28
80
  async action(signature) { try {
29
81
  return await this.request(`/actions/${encodeURIComponent(signature)}`);
@@ -36,25 +88,63 @@ export class ArxiumRpc {
36
88
  blocks(from, to) { return this.request(`/blocks?from=${from}&to=${to}`); }
37
89
  block(height) { return this.request(`/blocks/${height}`); }
38
90
  blockByHash(hash) { return this.request(`/blocks/by-hash/${encodeURIComponent(hash)}`); }
39
- validators() { return this.request("/validators"); }
91
+ /** State changes a block made (balances, assets, stake), for indexers. */
92
+ blockEffects(height) { return this.request(`/blocks/${height}/effects`); }
93
+ effects(from, to) { return this.request(`/effects?from=${from}&to=${to}`); }
94
+ /** Sorted validator addresses, at the tip or at `height`. */
95
+ validators(height) { return this.request(height === undefined ? "/validators" : `/validators?height=${height}`); }
96
+ validator(address) { return this.request(`/validators/${encodeURIComponent(address)}`); }
97
+ /** Voting power by validator address, at the tip or at `height`. */
98
+ validatorPower(height) { return this.request(height === undefined ? "/validators/power" : `/validators/power?height=${height}`); }
99
+ operatorValidators(operator) { return this.request(`/operators/${encodeURIComponent(operator)}/validators`); }
40
100
  finality() { return this.request("/finality"); }
101
+ /** Ids of stored equivocation/fault evidence; `evidence(id)` returns one. */
102
+ evidenceList() { return this.request("/evidence"); }
103
+ evidence(id) { return this.request(`/evidence/${encodeURIComponent(id)}`); }
104
+ /** Merkle proofs against a finalized state root, for light clients. */
105
+ accountProof(address) { return this.request(`/accounts/${encodeURIComponent(address)}/proof`); }
106
+ assetBalanceProof(address, assetRef) { return this.request(`/accounts/${encodeURIComponent(address)}/assets/${encodeURIComponent(assetRef)}/proof`); }
107
+ validatorProof(address) { return this.request(`/validators/${encodeURIComponent(address)}/proof`); }
41
108
  search(query) { return this.request(`/search?q=${encodeURIComponent(query)}`); }
42
109
  minStake() { return this.request("/min-stake"); }
43
110
  actionFee() { return this.request("/action-fee"); }
44
- async sendAction(options) { const account = await this.account(options.sender).catch((error) => { if (error instanceof RpcError && error.status === 404)
45
- return { nonce: 0 }; throw error; }); const signature = await signAction(options.privateKey, await this.genesisHash(), options.sender, account.nonce, options.payload); const action = submitBody(options.sender, account.nonce, signature, options.payload); await this.submit(action); const deadline = Date.now() + (options.timeoutMs ?? 120_000); while (Date.now() < deadline) {
46
- const status = await this.action(signature);
47
- if (status.status === "dropped")
48
- throw new Error(`action dropped: ${status.reason}`);
49
- if (status.status === "confirmed") {
50
- if (!options.finalized)
51
- return { action, status };
52
- const block = await this.block(status.height);
53
- if (block.finalized)
54
- return { action, status };
111
+ /** The chain's current fee parameters, for `actionFee(params, …)`. */
112
+ async feeParams() { const fee = await this.actionFee(); return { actionFee: BigInt(fee.action_fee), weightFee: BigInt(fee.weight_fee) }; }
113
+ /** Signs at the sender's current nonce, submits and polls until confirmed (or finalized). Calls for one sender run one after another. */
114
+ sendAction(options) {
115
+ const previous = this.senders.get(options.sender) ?? Promise.resolve();
116
+ const run = previous.catch(() => undefined).then(() => this.sendNow(options));
117
+ const tail = run.catch(() => undefined);
118
+ this.senders.set(options.sender, tail);
119
+ void tail.then(() => { if (this.senders.get(options.sender) === tail)
120
+ this.senders.delete(options.sender); });
121
+ return run;
122
+ }
123
+ async sendNow(options) {
124
+ if (!options.privateKey === !options.sign)
125
+ throw new Error("sendAction needs exactly one of privateKey or sign");
126
+ const account = await this.account(options.sender).catch((error) => { if (error instanceof RpcError && error.status === 404)
127
+ return { nonce: 0 }; throw error; });
128
+ const genesis = await this.genesisHash(), nonce = account.nonce;
129
+ const signature = options.sign ? await options.sign(signingBytes(genesis, options.sender, nonce, options.payload), nonce) : await signAction(options.privateKey, genesis, options.sender, nonce, options.payload);
130
+ const action = submitBody(options.sender, nonce, signature, options.payload);
131
+ await this.submit(action);
132
+ const deadline = Date.now() + (options.timeoutMs ?? 120_000);
133
+ while (Date.now() < deadline) {
134
+ const status = await this.action(signature);
135
+ if (status.status === "dropped")
136
+ throw new ActionDroppedError(status.reason, action);
137
+ if (status.status === "confirmed") {
138
+ if (!options.finalized)
139
+ return { action, status };
140
+ const block = await this.block(status.height);
141
+ if (block.finalized)
142
+ return { action, status };
143
+ }
144
+ await new Promise((resolve) => setTimeout(resolve, options.pollIntervalMs ?? 1_000));
55
145
  }
56
- await new Promise((resolve) => setTimeout(resolve, options.pollIntervalMs ?? 1_000));
57
- } throw new Error("timed out waiting for action"); }
146
+ throw new Error("timed out waiting for action");
147
+ }
58
148
  }
59
149
  /** Stateful witnesses require the policy at the action's pre-state, not today's
60
150
  * policy. This checks cryptography; circuit execution proves limits and scope. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arxiumlabs/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.9.0",
4
4
  "description": "Arxium TypeScript SDK and arx CLI",
5
5
  "type": "module",
6
6
  "private": false,