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