@arxiumlabs/sdk 0.8.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -5
- package/SIGNERS.md +132 -0
- package/dist/actions.d.ts +128 -2
- package/dist/actions.js +191 -5
- package/dist/cli.js +65 -5
- package/dist/fees.d.ts +4 -0
- package/dist/fees.js +29 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/policy.d.ts +5 -1
- package/dist/policy.js +2 -0
- package/dist/rpc.d.ts +116 -3
- package/dist/rpc.js +49 -12
- package/dist/signer.d.ts +63 -0
- package/dist/signer.js +107 -0
- package/package.json +4 -3
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),
|
|
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 === "
|
|
332
|
-
: throwUsage("
|
|
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
package/dist/index.js
CHANGED
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);
|