@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/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,7 +12,8 @@ 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
+ deployModule: 46, callModule: 47,
16
17
  };
17
18
  /** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
18
19
  export const TOKEN_ACTION = 39;
@@ -79,6 +80,159 @@ export function encodeRenounceMint(tokenRef) { return token("renounceMint").stri
79
80
  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(); }
80
81
  /** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
81
82
  export function encodeBlockAttestor() { return new Writer().varint(ACTION_VARIANT.blockAttestor).bytes(); }
83
+ /** `DvpTerms.domain`: no `ActionPayload` can start with it, so a consent never doubles as an ordinary action signature. */
84
+ export const DVP_DOMAIN = 0xffff_ffff;
85
+ /** Protocol u64 fields must not inherit the generic writer's wider u128 range. */
86
+ function exactU64(value, field) {
87
+ if (typeof value === "number" && !Number.isSafeInteger(value))
88
+ throw new RangeError(`${field} must be an exact u64; use bigint above Number.MAX_SAFE_INTEGER`);
89
+ const integer = BigInt(value);
90
+ if (integer < 0n || integer > 0xffffffffffffffffn)
91
+ throw new RangeError(`${field} out of u64 range`);
92
+ return integer;
93
+ }
94
+ /**
95
+ * What the counterparty signs, mirroring `dvp_consent(…).signing_bytes`: the order, bound to the genesis hash and the
96
+ * counterparty's current nonce, which settling consumes. Sending any other action first cancels the consent. A multisig
97
+ * counterparty's members each sign these bytes and combine them with `multisigSignature`; a programmable account's owners
98
+ * combine theirs with `assemblePolicySignature("owner", …)`.
99
+ */
100
+ export function dvpConsentBytes(genesis, initiator, order, counterpartyNonce) {
101
+ const expiresAt = exactU64(order.expiresAt, "expiresAt");
102
+ const w = new Writer().varint(DVP_DOMAIN).string(initiator);
103
+ writeSpendAsset(w, order.give);
104
+ w.varint(order.giveAmount);
105
+ writeSpendAsset(w, order.take);
106
+ w.varint(order.takeAmount).varint(expiresAt);
107
+ return signingBytes(genesis, order.counterparty, counterpartyNonce, w.bytes());
108
+ }
109
+ 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))))); }
110
+ export function encodeSettleDvp(order, counterpartySignature) {
111
+ const expiresAt = exactU64(order.expiresAt, "expiresAt");
112
+ const w = new Writer().varint(ACTION_VARIANT.settleDvp);
113
+ writeSpendAsset(w, order.give);
114
+ w.varint(order.giveAmount).string(order.counterparty);
115
+ writeSpendAsset(w, order.take);
116
+ w.varint(order.takeAmount).varint(expiresAt).string(counterpartySignature);
117
+ return w.bytes();
118
+ }
119
+ /** `ActionPayload::Collateral(CollateralAction)` is variant 44; these are `CollateralAction`'s own discriminants, written after it. */
120
+ export const COLLATERAL_ACTION = 44;
121
+ export const COLLATERAL_VARIANT = { pledge: 0, markPledge: 1, topUpPledge: 2, substitutePledge: 3, releasePledge: 4, seizePledge: 5 };
122
+ /** `unitPrice` is quote units per base unit of the pledged asset, times this: `PRICE_SCALE` is a price of 1.00. */
123
+ export const PRICE_SCALE = 1000000000n;
124
+ /** `PledgeConsent.domain` and `SubstituteConsent.domain`: like `DVP_DOMAIN`, no consent doubles as an action signature. */
125
+ export const PLEDGE_DOMAIN = 0xffff_fffe;
126
+ export const SUBSTITUTE_DOMAIN = 0xffff_fffd;
127
+ function exactU16(value, field) {
128
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff)
129
+ throw new RangeError(`${field} out of u16 range`);
130
+ return value;
131
+ }
132
+ /** Mirrors `pledge_id`: `sha256("xc-pledge:" ‖ pledgor ‖ ":" ‖ nonce as 8 big-endian bytes)`, where `nonce` is the opening `Pledge`'s. */
133
+ export async function pledgeId(pledgor, nonce) {
134
+ const prefix = new TextEncoder().encode(`xc-pledge:${pledgor}:`), n = exactU64(nonce, "nonce"), input = new Uint8Array(prefix.length + 8);
135
+ input.set(prefix);
136
+ new DataView(input.buffer).setBigUint64(prefix.length, n);
137
+ return `0x${toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", input)))}`;
138
+ }
139
+ /**
140
+ * What the beneficiary signs, mirroring `pledge_consent(…).signing_bytes`: the terms, bound to the genesis hash and the
141
+ * beneficiary's current nonce, which the pledge consumes. Multisig and programmable beneficiaries combine signatures as for `dvpConsentBytes`.
142
+ */
143
+ export function pledgeConsentBytes(genesis, pledgor, terms, beneficiaryNonce) {
144
+ const w = new Writer().varint(PLEDGE_DOMAIN).string(pledgor).string(terms.valuationAgent ?? terms.beneficiary);
145
+ writeSpendAsset(w, terms.asset);
146
+ w.varint(terms.amount).varint(exactU16(terms.haircutBps, "haircutBps")).varint(terms.exposure).varint(terms.unitPrice).varint(exactU64(terms.curePeriodBlocks, "curePeriodBlocks"));
147
+ w.option(terms.maturity, (ww, m) => ww.varint(exactU64(m, "maturity")));
148
+ return signingBytes(genesis, terms.beneficiary, beneficiaryNonce, w.bytes());
149
+ }
150
+ export async function signPledgeConsent(privateKey, genesis, pledgor, terms, beneficiaryNonce) { return toHex(new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, asBuffer(pledgeConsentBytes(genesis, pledgor, terms, beneficiaryNonce))))); }
151
+ const collateral = (name) => new Writer().varint(COLLATERAL_ACTION).varint(COLLATERAL_VARIANT[name]);
152
+ export function encodePledge(terms, beneficiarySignature) {
153
+ const w = collateral("pledge").string(terms.beneficiary).option(terms.valuationAgent, (ww, agent) => ww.string(agent));
154
+ writeSpendAsset(w, terms.asset);
155
+ w.varint(terms.amount).varint(exactU16(terms.haircutBps, "haircutBps")).varint(terms.exposure).varint(terms.unitPrice).varint(exactU64(terms.curePeriodBlocks, "curePeriodBlocks"));
156
+ return w.option(terms.maturity, (ww, m) => ww.varint(exactU64(m, "maturity"))).string(beneficiarySignature).bytes();
157
+ }
158
+ /** 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. */
159
+ export function encodeMarkPledge(pledge, unitPrice, exposure) { return collateral("markPledge").string(pledge).varint(unitPrice).varint(exposure).bytes(); }
160
+ /** The pledgor adds `amount` of the pledged asset. */
161
+ export function encodeTopUpPledge(pledge, amount) { return collateral("topUpPledge").string(pledge).varint(amount).bytes(); }
162
+ /** What the valuation agent signs for a substitution, mirroring `substitute_consent(…).signing_bytes`, at the agent's nonce (consumed). */
163
+ export function substituteConsentBytes(genesis, agent, agentNonce, pledge, asset, amount, unitPrice) {
164
+ const w = new Writer().varint(SUBSTITUTE_DOMAIN).string(pledge);
165
+ writeSpendAsset(w, asset);
166
+ return signingBytes(genesis, agent, agentNonce, w.varint(amount).varint(unitPrice).bytes());
167
+ }
168
+ export async function signSubstituteConsent(privateKey, genesis, agent, agentNonce, pledge, asset, amount, unitPrice) { return toHex(new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, asBuffer(substituteConsentBytes(genesis, agent, agentNonce, pledge, asset, amount, unitPrice))))); }
169
+ /** The pledgor swaps the collateral for `amount` of `asset` at `unitPrice`, with the agent's `signSubstituteConsent`. */
170
+ export function encodeSubstitutePledge(pledge, asset, amount, unitPrice, agentSignature) {
171
+ const w = collateral("substitutePledge").string(pledge);
172
+ writeSpendAsset(w, asset);
173
+ return w.varint(amount).varint(unitPrice).string(agentSignature).bytes();
174
+ }
175
+ /** `amount` back to the pledgor: the beneficiary at any time, the pledgor in full once the exposure is marked repaid. */
176
+ export function encodeReleasePledge(pledge, amount) { return collateral("releasePledge").string(pledge).varint(amount).bytes(); }
177
+ /** In default only, by the beneficiary: everything (`full`) or the units worth the exposure, the rest returned. Closes the pledge. */
178
+ export function encodeSeizePledge(pledge, full) { return collateral("seizePledge").string(pledge).bool(full).bytes(); }
179
+ /** `ActionPayload::Venue(VenueAction)` is variant 45; these are `VenueAction`'s own discriminants, written after it. */
180
+ export const VENUE_ACTION = 45;
181
+ export const VENUE_VARIANT = { settleVenue: 0, cancelVenueOrder: 1 };
182
+ /** `VenueOrder.domain`: like `DVP_DOMAIN`, an order signature never doubles as an action or a consent. */
183
+ export const VENUE_ORDER_DOMAIN = 0xffff_fffc;
184
+ /** Blocks an order may expire ahead of the height it settles at (one day at 2 s blocks). `signVenueOrder` callers should stay within it. */
185
+ export const MAX_ORDER_LIFETIME = 43200n;
186
+ /** Trades per `settleVenue`. */
187
+ export const MAX_VENUE_TRADES = 32;
188
+ function writeVenueTerms(w, terms) {
189
+ const expiresAt = exactU64(terms.expiresAt, "expiresAt");
190
+ w.varint(VENUE_ORDER_DOMAIN).string(terms.venue);
191
+ writeSpendAsset(w, terms.give);
192
+ w.varint(terms.giveAmount);
193
+ writeSpendAsset(w, terms.take);
194
+ return w.varint(terms.takeAmount).varint(expiresAt);
195
+ }
196
+ /**
197
+ * What an order's owner signs, mirroring `venue_order(…).signing_bytes`: the terms as an action from the owner with
198
+ * `nonce = orderId`. Each `(owner, orderId)` settles at most once; pick a random u64. Multisig and programmable owners
199
+ * combine signatures as for `dvpConsentBytes`.
200
+ */
201
+ export function venueOrderBytes(genesis, owner, orderId, terms) {
202
+ return signingBytes(genesis, owner, exactU64(orderId, "orderId"), writeVenueTerms(new Writer(), terms).bytes());
203
+ }
204
+ export async function signVenueOrder(privateKey, genesis, owner, orderId, terms) { return toHex(new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, asBuffer(venueOrderBytes(genesis, owner, orderId, terms))))); }
205
+ const venue = (name) => new Writer().varint(VENUE_ACTION).varint(VENUE_VARIANT[name]);
206
+ function writeSignedOrder(w, order) {
207
+ w.string(order.owner).varint(exactU64(order.orderId, "orderId"));
208
+ return writeVenueTerms(w, order.terms).string(order.signature);
209
+ }
210
+ /** The venue (sender, named in every order) settles 1..`MAX_VENUE_TRADES` mirrored trades, all or nothing, and pays the fee. */
211
+ export function encodeSettleVenue(trades) {
212
+ if (trades.length < 1 || trades.length > MAX_VENUE_TRADES)
213
+ throw new RangeError(`a venue settlement carries 1 to ${MAX_VENUE_TRADES} trades`);
214
+ return venue("settleVenue").vec(trades, (w, t) => { writeSignedOrder(w, t.maker); writeSignedOrder(w, t.taker); }).bytes();
215
+ }
216
+ /** The owner kills its order. `expiresAt` must be the order's own expiry: the chain forgets the cancel after it. */
217
+ export function encodeCancelVenueOrder(orderId, expiresAt) { return venue("cancelVenueOrder").varint(exactU64(orderId, "orderId")).varint(exactU64(expiresAt, "expiresAt")).bytes(); }
218
+ /** 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}`. */
219
+ export function encodeDeployModule(code, salt = 0) { return new Writer().varint(ACTION_VARIANT.deployModule).vec(Array.from(code), (w, b) => w.u8(b)).varint(exactU64(salt, "salt")).bytes(); }
220
+ /** 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. */
221
+ export function encodeCallModule(module, input, gasLimit) { return new Writer().varint(ACTION_VARIANT.callModule).string(module).vec(Array.from(input), (w, b) => w.u8(b)).varint(exactU64(gasLimit, "gasLimit")).bytes(); }
222
+ /** Mirrors `module_address` in `arxd/runtime/src/module.rs`: where a module deployed by `deployer` with this `code` and `salt` lives. */
223
+ export async function moduleAddress(deployer, code, salt = 0) {
224
+ const sha = async (bytes) => new Uint8Array(await crypto.subtle.digest("SHA-256", asBuffer(bytes))), text = new TextEncoder();
225
+ const tail = new Uint8Array(8);
226
+ new DataView(tail.buffer).setBigUint64(0, exactU64(salt, "salt"));
227
+ const parts = [text.encode("xc-module:"), text.encode(deployer), text.encode(":"), await sha(code), tail];
228
+ const preimage = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
229
+ let at = 0;
230
+ for (const p of parts) {
231
+ preimage.set(p, at);
232
+ at += p.length;
233
+ }
234
+ return encodeAddress(await sha(preimage));
235
+ }
82
236
  /** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
83
237
  export const GOVERNANCE_ACTION = { addAttestor: 4, removeAttestor: 5, unblockAttestor: 6 };
84
238
  /** 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. */
@@ -94,6 +248,7 @@ export function encodeVoteProposal(proposal, approve) { return new Writer().vari
94
248
  export function encodeExecuteProposal(proposal) { return new Writer().varint(ACTION_VARIANT.executeProposal).varint(BigInt(proposal)).bytes(); }
95
249
  export function encodeSetAssetLimits(asset, maxHolders, maxBalancePerHolder = null, maxAttestationAge = null) { return new Writer().varint(ACTION_VARIANT.setAssetLimits).string(asset).option(maxHolders, (w, value) => w.varint(value)).option(maxBalancePerHolder, (w, value) => w.varint(value)).option(maxAttestationAge, (w, value) => w.varint(value)).bytes(); }
96
250
  const nullableBig = (value) => value == null ? null : BigInt(value);
251
+ const orderInput = (o) => ({ owner: o.owner, orderId: BigInt(o.orderId), terms: { venue: o.terms.venue, give: o.terms.give, giveAmount: BigInt(o.terms.giveAmount), take: o.terms.take, takeAmount: BigInt(o.terms.takeAmount), expiresAt: BigInt(o.terms.expiresAt) }, signature: o.signature });
97
252
  export function encodePayload({ name, input }) {
98
253
  if (name in POLICY_VARIANT)
99
254
  return encodeAccountPolicy(name, input);
@@ -136,16 +291,27 @@ export function encodePayload({ name, input }) {
136
291
  case "executeProposal": return encodeExecuteProposal(BigInt(input.proposal));
137
292
  case "applyAttestor": return encodeApplyAttestor(input.name, input.owners, input.threshold, input.evidenceHash, input.evidenceUri);
138
293
  case "blockAttestor": return encodeBlockAttestor();
294
+ 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);
139
295
  case "createToken": return encodeCreateToken(input.symbol, input.name, input.decimals, BigInt(input.initialSupply), nullableBig(input.maxSupply), input.mintable);
140
296
  case "mintToken": return encodeMintToken(input.token, input.to, BigInt(input.amount));
141
297
  case "transferToken": return encodeTransferToken(input.token, input.to, BigInt(input.amount));
142
298
  case "burnToken": return encodeBurnToken(input.token, BigInt(input.amount));
143
299
  case "renounceMint": return encodeRenounceMint(input.token);
300
+ case "pledge": return encodePledge({ beneficiary: input.beneficiary, valuationAgent: input.valuationAgent, asset: input.asset, amount: BigInt(input.amount), haircutBps: input.haircutBps, exposure: BigInt(input.exposure), unitPrice: BigInt(input.unitPrice), curePeriodBlocks: BigInt(input.curePeriodBlocks), maturity: nullableBig(input.maturity) }, input.beneficiarySignature);
301
+ case "markPledge": return encodeMarkPledge(input.pledge, BigInt(input.unitPrice), BigInt(input.exposure));
302
+ case "topUpPledge": return encodeTopUpPledge(input.pledge, BigInt(input.amount));
303
+ case "substitutePledge": return encodeSubstitutePledge(input.pledge, input.asset, BigInt(input.amount), BigInt(input.unitPrice), input.agentSignature);
304
+ case "releasePledge": return encodeReleasePledge(input.pledge, BigInt(input.amount));
305
+ case "seizePledge": return encodeSeizePledge(input.pledge, input.full);
306
+ case "settleVenue": return encodeSettleVenue(input.trades.map((t) => ({ maker: orderInput(t.maker), taker: orderInput(t.taker) })));
307
+ case "cancelVenueOrder": return encodeCancelVenueOrder(BigInt(input.orderId), BigInt(input.expiresAt));
308
+ case "deployModule": return encodeDeployModule(Uint8Array.from(input.code), BigInt(input.salt));
309
+ case "callModule": return encodeCallModule(input.module, Uint8Array.from(input.input), BigInt(input.gasLimit));
144
310
  default: throw new Error(`unsupported action: ${name}`);
145
311
  }
146
312
  }
147
313
  const invert = (table) => Object.fromEntries(Object.entries(table).map(([key, value]) => [value, key]));
148
- const VARIANT_NAME = invert(ACTION_VARIANT), TOKEN_NAME = invert(TOKEN_VARIANT), CLASS_NAME = invert(CLASS), TOPIC_NAME = invert(TOPIC), GOVERNANCE_NAME = invert(GOVERNANCE_ACTION);
314
+ const VARIANT_NAME = invert(ACTION_VARIANT), TOKEN_NAME = invert(TOKEN_VARIANT), COLLATERAL_NAME = invert(COLLATERAL_VARIANT), VENUE_NAME = invert(VENUE_VARIANT), CLASS_NAME = invert(CLASS), TOPIC_NAME = invert(TOPIC), GOVERNANCE_NAME = invert(GOVERNANCE_ACTION);
149
315
  const known = (value, what) => { if (value === undefined)
150
316
  throw new Error(`unknown ${what}`); return value; };
151
317
  /**
@@ -161,7 +327,7 @@ export function decodePayload(payload) {
161
327
  throw new Error("payload is not in canonical form");
162
328
  return decoded;
163
329
  }
164
- const name = outer === TOKEN_ACTION ? known(TOKEN_NAME[Number(r.varint())], "token action") : known(VARIANT_NAME[outer], "action variant");
330
+ const name = outer === TOKEN_ACTION ? known(TOKEN_NAME[Number(r.varint())], "token action") : outer === COLLATERAL_ACTION ? known(COLLATERAL_NAME[Number(r.varint())], "collateral action") : outer === VENUE_ACTION ? known(VENUE_NAME[Number(r.varint())], "venue action") : known(VARIANT_NAME[outer], "action variant");
165
331
  const str = () => r.string(), amount = () => r.varint().toString(), bytes = () => r.vec((rr) => rr.u8()), topics = () => r.vec((rr) => known(TOPIC_NAME[Number(rr.varint())], "claim topic"));
166
332
  const read = {
167
333
  transfer: () => ({ to: str(), amount: amount() }),
@@ -202,11 +368,31 @@ export function decodePayload(payload) {
202
368
  executeProposal: () => ({ proposal: amount() }),
203
369
  applyAttestor: () => ({ name: str(), owners: r.vec((rr) => rr.string()), threshold: r.u8(), evidenceHash: str(), evidenceUri: str() }),
204
370
  blockAttestor: () => ({}),
371
+ settleDvp: () => ({ give: readSpendAsset(r), giveAmount: amount(), counterparty: str(), take: readSpendAsset(r), takeAmount: amount(), expiresAt: amount(), counterpartySignature: str() }),
205
372
  createToken: () => ({ symbol: str(), name: str(), decimals: r.u8(), initialSupply: amount(), maxSupply: r.option(amount), mintable: r.bool() }),
206
373
  mintToken: () => ({ token: str(), to: str(), amount: amount() }),
207
374
  transferToken: () => ({ token: str(), to: str(), amount: amount() }),
208
375
  burnToken: () => ({ token: str(), amount: amount() }),
209
376
  renounceMint: () => ({ token: str() }),
377
+ pledge: () => ({ beneficiary: str(), valuationAgent: r.option(str), asset: readSpendAsset(r), amount: amount(), haircutBps: Number(r.varint()), exposure: amount(), unitPrice: amount(), curePeriodBlocks: amount(), maturity: r.option(amount), beneficiarySignature: str() }),
378
+ markPledge: () => ({ pledge: str(), unitPrice: amount(), exposure: amount() }),
379
+ topUpPledge: () => ({ pledge: str(), amount: amount() }),
380
+ substitutePledge: () => ({ pledge: str(), asset: readSpendAsset(r), amount: amount(), unitPrice: amount(), agentSignature: str() }),
381
+ releasePledge: () => ({ pledge: str(), amount: amount() }),
382
+ seizePledge: () => ({ pledge: str(), full: r.bool() }),
383
+ settleVenue: () => {
384
+ const order = () => {
385
+ const owner = str(), orderId = amount();
386
+ if (Number(r.varint()) !== VENUE_ORDER_DOMAIN)
387
+ throw new Error("not a venue order");
388
+ const terms = { venue: str(), give: readSpendAsset(r), giveAmount: amount(), take: readSpendAsset(r), takeAmount: amount(), expiresAt: amount() };
389
+ return { owner, orderId, terms, signature: str() };
390
+ };
391
+ return { trades: r.vec(() => ({ maker: order(), taker: order() })) };
392
+ },
393
+ cancelVenueOrder: () => ({ orderId: amount(), expiresAt: amount() }),
394
+ deployModule: () => ({ code: bytes(), salt: amount() }),
395
+ callModule: () => ({ module: str(), input: bytes(), gasLimit: amount() }),
210
396
  };
211
397
  const decoded = { name, input: read[name]() };
212
398
  r.done();
@@ -216,7 +402,7 @@ export function decodePayload(payload) {
216
402
  }
217
403
  /** Mirrors `Action::signing_bytes`: the chain's 32-byte genesis hash (`GET /genesis-hash`) binds the signature to one chain, so an action signed for chain A is rejected on chain B. */
218
404
  export function signingBytes(genesis, sender, nonce, payload) { if (genesis.length !== 32)
219
- throw new Error("genesis hash must be 32 bytes"); return new Writer().raw(genesis).string(sender).varint(nonce).raw(payload).bytes(); }
405
+ throw new Error("genesis hash must be 32 bytes"); return new Writer().raw(genesis).string(sender).varint(exactU64(nonce, "nonce")).raw(payload).bytes(); }
220
406
  export async function signAction(privateKey, genesis, sender, nonce, payload) { return toHex(new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, asBuffer(signingBytes(genesis, sender, nonce, payload))))); }
221
407
  export function submitBody(sender, nonce, signature, payload) { return { sender, nonce, signature, payload: Array.from(payload) }; }
222
408
  /** M-of-N sender, mirrors `xc_primitives::multisig_address`. Every member signs with plain `signAction(key, genesis, multisigAddress, nonce, payload)`; `multisigSignature` combines exactly `threshold` of them into the action's `signature` field. */
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ 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, 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, } 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, encodeDeployModule, encodeCallModule, encodeSettleVenue, encodeCancelVenueOrder, signVenueOrder, MAX_ORDER_LIFETIME, } from "./index.js";
7
7
  class UsageError extends Error {
8
8
  }
9
9
  function output(value, json) { stdout.write(!json && typeof value === "string" ? `${value}\n` : `${JSON.stringify(value, bigintReplacer, 2)}\n`); }
@@ -123,7 +123,11 @@ const USAGE = {
123
123
  "split-asset": "<asset-ref> <snapshot-height> <numerator> <denominator>",
124
124
  "create-token": "<symbol> <name> <decimals> <initial-supply> <max-supply|-> mintable|fixed", "mint-token": "<token-ref> <to> <amount>",
125
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
+ "venue-settle": "<trades.json>", "venue-cancel": "<order-id> <expires-at-height>",
128
+ "module-deploy": "<module.wasm> [salt]", "module-call": "<module-address> <gas-limit> [input-hex]",
126
129
  };
130
+ const spendArg = (value) => value === "native" ? "native" : { asset: value };
127
131
  function morePayload(action, args) {
128
132
  const usage = USAGE[action] ?? throwUsage(`unsupported action: ${action}`);
129
133
  const arg = (i) => args[i] ?? throwUsage(`${action} needs ${usage}`);
@@ -158,6 +162,15 @@ function morePayload(action, args) {
158
162
  case "mint-token": return encodeMintToken(arg(0), arg(1), int(2));
159
163
  case "transfer-token": return encodeTransferToken(arg(0), arg(1), int(2));
160
164
  case "burn-token": return encodeBurnToken(arg(0), int(1));
165
+ 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));
166
+ case "venue-settle": {
167
+ const raw = JSON.parse(arg(0));
168
+ const order = (o) => ({ owner: o.owner, orderId: BigInt(o.orderId), terms: { venue: o.terms.venue, give: o.terms.give, giveAmount: BigInt(o.terms.giveAmount), take: o.terms.take, takeAmount: BigInt(o.terms.takeAmount), expiresAt: BigInt(o.terms.expiresAt) }, signature: o.signature });
169
+ return encodeSettleVenue(raw.map((t) => ({ maker: order(t.maker), taker: order(t.taker) })));
170
+ }
171
+ case "venue-cancel": return encodeCancelVenueOrder(int(0), int(1));
172
+ case "module-deploy": return encodeDeployModule(hexArg(arg(0)), args[1] === undefined ? 0n : int(1));
173
+ case "module-call": return encodeCallModule(arg(0), args[2] === undefined ? new Uint8Array() : hexArg(args[2]), int(1));
161
174
  default: return encodeRenounceMint(arg(0));
162
175
  }
163
176
  }
@@ -177,7 +190,7 @@ async function main() {
177
190
  return;
178
191
  }
179
192
  if (!command)
180
- throwUsage("use: arx keys|sign|submit|send|query|verify");
193
+ throwUsage("use: arx keys|sign|submit|send|dvp-consent|venue-order|query|verify");
181
194
  if (command === "keys") {
182
195
  const [operation, ...keyArgs] = rest;
183
196
  if (operation === "new") {
@@ -284,8 +297,44 @@ async function main() {
284
297
  }
285
298
  throwUsage("use: arx multisig address|build|sign|combine|link");
286
299
  }
300
+ // The counterparty's half of a DvP: sign the order at its current nonce. The initiator sends it with `arx send settle-dvp`.
301
+ if (command === "dvp-consent") {
302
+ 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;
303
+ if (!initiator || !give || !take || ![giveAmount, takeAmount, expiresAt].every((v) => /^\d+$/.test(v ?? "")))
304
+ throwUsage(use);
305
+ const { file, privateKey } = await loadKey(globals.key);
306
+ const nonce = globals.nonce ?? await rpc.account(file.address).then((account) => account.nonce, (error) => { if (error instanceof RpcError && error.status === 404)
307
+ return 0; throw error; });
308
+ const order = { give: spendArg(give), giveAmount: BigInt(giveAmount), counterparty: file.address, take: spendArg(take), takeAmount: BigInt(takeAmount), expiresAt: BigInt(expiresAt) };
309
+ const signature = await signDvpConsent(privateKey, await rpc.genesisHash(), initiator, order, nonce);
310
+ output({ counterparty: file.address, counterpartyNonce: nonce, signature, send: ["settle-dvp", give, giveAmount, file.address, take, takeAmount, expiresAt, signature] }, true);
311
+ return;
312
+ }
313
+ // One party's order for a venue: sign it with a fresh random id. The venue pairs two of these into `arx send venue-settle`.
314
+ if (command === "venue-order") {
315
+ const use = "venue-order needs <venue> <give-asset-ref|native> <give-amount> <take-asset-ref|native> <take-amount> <expires-at-height> [--order-id n]";
316
+ const idFlag = rest.indexOf("--order-id"), idText = idFlag >= 0 ? rest.splice(idFlag, 2)[1] : undefined;
317
+ if (idFlag >= 0 && !/^\d+$/.test(idText ?? ""))
318
+ throwUsage(use);
319
+ const orderId = idText !== undefined ? BigInt(idText) : new DataView(crypto.getRandomValues(new Uint8Array(8)).buffer).getBigUint64(0);
320
+ const [venueAddress, give, giveAmount, take, takeAmount, expiresAt] = rest;
321
+ if (!venueAddress || !give || !take || !/^\d+$/.test(giveAmount ?? "") || !/^\d+$/.test(takeAmount ?? "") || !/^\d+$/.test(expiresAt ?? ""))
322
+ throwUsage(use);
323
+ const tip = BigInt((await rpc.status()).tip_height);
324
+ if (BigInt(expiresAt) > tip + MAX_ORDER_LIFETIME)
325
+ throwUsage(`expires-at must be within ${MAX_ORDER_LIFETIME} blocks of the tip (${tip})`);
326
+ const { file, privateKey } = await loadKey(globals.key), genesis = await rpc.genesisHash();
327
+ const terms = { venue: venueAddress, give: spendArg(give), giveAmount: BigInt(giveAmount), take: spendArg(take), takeAmount: BigInt(takeAmount), expiresAt: BigInt(expiresAt) };
328
+ output({ owner: file.address, orderId, terms, signature: await signVenueOrder(privateKey, genesis, file.address, orderId, terms) }, true);
329
+ return;
330
+ }
287
331
  if (command === "sign" || command === "send") {
288
- const action = rest[0] ?? throwUsage(`${command} needs an action`), { file, privateKey } = await loadKey(globals.key), encoded = payload(action, rest.slice(1));
332
+ const action = rest[0] ?? throwUsage(`${command} needs an action`), { file, privateKey } = await loadKey(globals.key), actionArgs = rest.slice(1);
333
+ if (action === "venue-settle")
334
+ actionArgs[0] = await readFile(actionArgs[0] ?? throwUsage("venue-settle needs <trades.json>"), "utf8");
335
+ if (action === "module-deploy")
336
+ actionArgs[0] = toHex(new Uint8Array(await readFile(actionArgs[0] ?? throwUsage("module-deploy needs <module.wasm>"))));
337
+ const encoded = payload(action, actionArgs);
289
338
  if (command === "sign") {
290
339
  const nonce = globals.nonce ?? await rpc.account(file.address).then((account) => account.nonce, (error) => { if (error instanceof RpcError && error.status === 404)
291
340
  return 0; throw error; });
@@ -328,11 +377,22 @@ async function main() {
328
377
  : operation === "attestor" ? await rpc.attestor(value ?? throwUsage("attestor needs an address"))
329
378
  : operation === "validator" ? await rpc.validator(value ?? throwUsage("validator needs an address"))
330
379
  : operation === "stakes" ? await rpc.stakes(value ?? throwUsage("stakes needs an address"))
331
- : operation === "fee" ? await rpc.actionFee()
332
- : throwUsage("use: arx query status|account|block|action|validators|validator|finality|search|assets [address]|asset|holders|balance|attestors|attestor|stakes|fee");
380
+ : operation === "module" ? await rpc.module(value ?? throwUsage("module needs an address"))
381
+ : operation === "module-state" ? await rpc.moduleState(value ?? throwUsage("module-state needs <address> <key-hex>"), rest[2] ?? throwUsage("module-state needs <address> <key-hex>"))
382
+ : operation === "fee" ? await rpc.actionFee()
383
+ : throwUsage("use: arx query status|account|block|action|validators|validator|finality|search|assets [address]|asset|holders|balance|attestors|attestor|stakes|module|module-state|fee");
333
384
  output(result, globals.json);
334
385
  return;
335
386
  }
387
+ // Dry-runs a module call as the key's address; no passphrase needed, nothing is signed or sent.
388
+ if (command === "simulate") {
389
+ const [module, gas, inputHex] = rest;
390
+ const caller = (await readKeyFile(globals.key)).address;
391
+ if (!module || !gas || !/^\d+$/.test(gas))
392
+ throwUsage("simulate needs <module-address> <gas-limit> [input-hex] and --key");
393
+ output(await rpc.simulateModule(module, { caller, input: inputHex === undefined ? new Uint8Array() : hexArg(inputHex), gasLimit: BigInt(gas) }), globals.json);
394
+ return;
395
+ }
336
396
  throwUsage(`unknown command: ${command}`);
337
397
  }
338
398
  main().catch((error) => { const usage = error instanceof UsageError; stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = usage ? 2 : 1; });
package/dist/fees.d.ts CHANGED
@@ -3,6 +3,10 @@ export type FeeParams = {
3
3
  actionFee: bigint;
4
4
  weightFee: bigint;
5
5
  };
6
+ /** Fuel one weight unit pays for (`FUEL_PER_WEIGHT` in `arxd/runtime/src/module.rs`). */
7
+ export declare const FUEL_PER_WEIGHT = 50;
8
+ /** Dispute-artifact pricing for module actions (`BLOCK_PROOF_KEYS`, `PROOF_WEIGHT_PER_KEY`, `MODULE_BASE_KEYS`, `DEPLOY_WEIGHT_PER_CODE_BYTE`, `COST_READ` in metering.rs and `xc_wasm`). */
9
+ export declare const BLOCK_PROOF_KEYS = 64, PROOF_WEIGHT_PER_KEY = 15000, MODULE_BASE_KEYS = 4, DEPLOY_WEIGHT_PER_CODE_BYTE = 7, COST_READ = 1000;
6
10
  /** `POLICY_AUTHORIZATION_WEIGHT`: added when a stateful account policy authorizes the action. */
7
11
  export declare const POLICY_AUTHORIZATION_WEIGHT = 5000;
8
12
  /** A single-key ed25519 signature: 64 bytes as hex. */
package/dist/fees.js CHANGED
@@ -1,7 +1,7 @@
1
- import { ACTION_VARIANT, TOKEN_ACTION, TOKEN_VARIANT } from "./actions.js";
1
+ import { ACTION_VARIANT, COLLATERAL_ACTION, COLLATERAL_VARIANT, TOKEN_ACTION, TOKEN_VARIANT, VENUE_ACTION, VENUE_VARIANT } from "./actions.js";
2
2
  import { Reader } from "./bincode.js";
3
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. */
4
+ /** `base_weight` in metering.rs, by `ActionPayload` discriminant. Token, collateral, venue and account-policy actions are handled below. */
5
5
  const BASE_WEIGHT = {
6
6
  [ACTION_VARIANT.transfer]: 50,
7
7
  [ACTION_VARIANT.stake]: 100, [ACTION_VARIANT.unstake]: 100,
@@ -20,10 +20,15 @@ const BASE_WEIGHT = {
20
20
  [ACTION_VARIANT.transferIssuer]: 100, [ACTION_VARIANT.setAssetMetadataUri]: 100,
21
21
  [ACTION_VARIANT.snapshotHolders]: 2_000, [ACTION_VARIANT.distributeToHolders]: 15_000,
22
22
  [ACTION_VARIANT.redeemHolders]: 10_000, [ACTION_VARIANT.splitAsset]: 10_000,
23
+ [ACTION_VARIANT.settleDvp]: 350,
23
24
  [ACTION_VARIANT.submitProposal]: 150, [ACTION_VARIANT.voteProposal]: 150, [ACTION_VARIANT.executeProposal]: 150,
24
25
  5 /* SubmitEquivocationEvidence */: 2_000,
25
26
  15 /* SubmitExecutionFault */: 500_000,
26
27
  };
28
+ /** Fuel one weight unit pays for (`FUEL_PER_WEIGHT` in `arxd/runtime/src/module.rs`). */
29
+ export const FUEL_PER_WEIGHT = 50;
30
+ /** Dispute-artifact pricing for module actions (`BLOCK_PROOF_KEYS`, `PROOF_WEIGHT_PER_KEY`, `MODULE_BASE_KEYS`, `DEPLOY_WEIGHT_PER_CODE_BYTE`, `COST_READ` in metering.rs and `xc_wasm`). */
31
+ export const BLOCK_PROOF_KEYS = 64, PROOF_WEIGHT_PER_KEY = 15_000, MODULE_BASE_KEYS = 4, DEPLOY_WEIGHT_PER_CODE_BYTE = 7, COST_READ = 1_000;
27
32
  /** `POLICY_AUTHORIZATION_WEIGHT`: added when a stateful account policy authorizes the action. */
28
33
  export const POLICY_AUTHORIZATION_WEIGHT = 5_000;
29
34
  /** A single-key ed25519 signature: 64 bytes as hex. */
@@ -36,6 +41,28 @@ function baseWeight(payload) {
36
41
  return Number(r.varint()) === TOKEN_VARIANT.createToken ? 150 : 100;
37
42
  if (variant === ACCOUNT_POLICY_ACTION)
38
43
  return 1_000;
44
+ if (variant === COLLATERAL_ACTION) {
45
+ const action = Number(r.varint());
46
+ return action === COLLATERAL_VARIANT.pledge || action === COLLATERAL_VARIANT.substitutePledge ? 350 : action === COLLATERAL_VARIANT.seizePledge ? 250 : 150;
47
+ }
48
+ if (variant === VENUE_ACTION) {
49
+ // `VENUE_SETTLE_BASE + trades × VENUE_TRADE_WEIGHT` in metering.rs; a cancel is flat.
50
+ return Number(r.varint()) === VENUE_VARIANT.settleVenue ? 100 + Number(r.varint()) * 800 : 100;
51
+ }
52
+ // `2_000 + 7 × code length` in metering.rs: the code rides in a dispute artifact about six times.
53
+ if (variant === ACTION_VARIANT.deployModule)
54
+ return 2_000 + r.vec((rr) => rr.u8()).length * DEPLOY_WEIGHT_PER_CODE_BYTE;
55
+ // The larger of `100 + ceil(gas_limit / FUEL_PER_WEIGHT)` and the proof bytes it could add (rows it could
56
+ // touch × PROOF_WEIGHT_PER_KEY) in metering.rs: priced by the limit, not by use. A tight limit is cheap.
57
+ if (variant === ACTION_VARIANT.callModule) {
58
+ r.string();
59
+ r.vec((rr) => rr.u8());
60
+ const gas = r.varint(), ceilDiv = (a, b) => (a + BigInt(b) - 1n) / BigInt(b);
61
+ const execution = 100n + ceilDiv(gas, FUEL_PER_WEIGHT);
62
+ const rows = ceilDiv(gas, COST_READ) + BigInt(MODULE_BASE_KEYS), keys = rows < BigInt(BLOCK_PROOF_KEYS) ? rows : BigInt(BLOCK_PROOF_KEYS);
63
+ const proofs = keys * BigInt(PROOF_WEIGHT_PER_KEY);
64
+ return Number(execution > proofs ? execution : proofs);
65
+ }
39
66
  const weight = BASE_WEIGHT[variant];
40
67
  if (weight === undefined)
41
68
  throw new Error(`unknown action variant: ${variant}`);
package/dist/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export * from "./keys.js";
6
6
  export * from "./rpc.js";
7
7
  export * from "./policy.js";
8
8
  export * from "./fees.js";
9
+ export * from "./signer.js";
package/dist/index.js CHANGED
@@ -6,3 +6,4 @@ export * from "./keys.js";
6
6
  export * from "./rpc.js";
7
7
  export * from "./policy.js";
8
8
  export * from "./fees.js";
9
+ export * from "./signer.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);