@arxiumlabs/sdk 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/dist/actions.d.ts +185 -0
- package/dist/actions.js +241 -0
- package/dist/amounts.d.ts +3 -0
- package/dist/amounts.js +5 -0
- package/dist/bech32.d.ts +9 -0
- package/dist/bech32.js +39 -0
- package/dist/bincode.d.ts +31 -0
- package/dist/bincode.js +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +264 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/keys.d.ts +37 -0
- package/dist/keys.js +36 -0
- package/dist/policy.d.ts +53 -0
- package/dist/policy.js +116 -0
- package/dist/rpc.d.ts +65 -0
- package/dist/rpc.js +74 -0
- package/package.json +41 -4
package/README.md
CHANGED
|
@@ -1,3 +1,139 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @arxiumlabs/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK and `arx` CLI for the Arxium network. It has zero runtime dependencies and requires Node 20 or later. The SDK uses WebCrypto Ed25519 and also runs in browsers and Workers.
|
|
4
|
+
|
|
5
|
+
Builder docs (endpoints, API keys, faucet, quickstart, RWA walkthrough): https://arxium.network/docs
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @arxiumlabs/sdk # library
|
|
11
|
+
npm install -g @arxiumlabs/sdk # arx CLI
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Releasing
|
|
15
|
+
|
|
16
|
+
Bump `version` here, run the release checks, then push a `sdk-vX.Y.Z` tag. `.github/workflows/sdk.yml` publishes to npm through trusted publishing (no token) with provenance.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
cd Arxium && cargo test -p arxd-runtime writes_typescript_signed_action_fixtures && git diff --exit-code sdk/ts/fixtures
|
|
20
|
+
cd sdk/ts && npm test
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## SDK quickstart
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { ArxiumRpc, encodeTransfer, arxToIum } from "@arxiumlabs/sdk";
|
|
27
|
+
|
|
28
|
+
const rpc = new ArxiumRpc({ rpc: "https://node.example", token: process.env.ARX_TOKEN });
|
|
29
|
+
const payload = encodeTransfer("arx1...", arxToIum("1.5"));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
All token amounts are `bigint`: `1 ARX = 1_000_000_000 IUM`.
|
|
33
|
+
|
|
34
|
+
### Chain binding
|
|
35
|
+
|
|
36
|
+
Every action signature covers the chain's 32-byte genesis hash (`GET /genesis-hash`, `rpc.genesisHash()`) ahead of sender, nonce and payload, so an action signed for one chain is rejected on any other (devnet vs. mainnet, or after a reset). `signingBytes`, `signAction` and `verifySignedAction` take it as an argument; `rpc.sendAction` fetches it for you.
|
|
37
|
+
|
|
38
|
+
### Multisig senders
|
|
39
|
+
|
|
40
|
+
An M-of-N address (up to 16 members) is derived from its policy. Each member signs the action exactly as a single-key sender would. You then combine exactly `threshold` of those signatures:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const sender = await multisigAddress(2, [pkA, pkB, pkC]); // member order doesn't matter
|
|
44
|
+
const genesis = await rpc.genesisHash(); // every signature binds the chain's genesis hash
|
|
45
|
+
const sigA = await signAction(keyA, genesis, sender, nonce, payload); // on A's machine
|
|
46
|
+
const sigC = await signAction(keyC, genesis, sender, nonce, payload); // on C's machine
|
|
47
|
+
const signature = multisigSignature(2, [pkA, pkB, pkC], [[pkA, sigA], [pkC, sigC]]);
|
|
48
|
+
await rpc.submit(submitBody(sender, nonce, signature, payload));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A co-signer should see what they're signing before they sign it. `decodePayload(bytes)` returns `{ name, input }` for every action the SDK can encode. It re-encodes the result and throws unless the bytes match exactly.
|
|
52
|
+
|
|
53
|
+
Set the resulting address as an admin role (genesis or `SetAdmin`) or as an asset issuer (`TransferIssuer`). To rotate members, move the role to a new multisig address.
|
|
54
|
+
|
|
55
|
+
## Key files
|
|
56
|
+
|
|
57
|
+
`arx` reads and writes Console's encrypted export JSON unchanged:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"address": "arx1...",
|
|
62
|
+
"publicKey": "...",
|
|
63
|
+
"version": 1,
|
|
64
|
+
"kdf": "PBKDF2-SHA256",
|
|
65
|
+
"iterations": 600000,
|
|
66
|
+
"salt": "...",
|
|
67
|
+
"iv": "...",
|
|
68
|
+
"ciphertext": "...",
|
|
69
|
+
"network": "arx-devnet"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`network` is a display label. It is not a signing domain. The CLI verifies that a decrypted private key matches `address` and `publicKey`.
|
|
74
|
+
|
|
75
|
+
Set `ARX_PASSPHRASE` for non-interactive use. Otherwise `arx` prompts on a terminal. A non-terminal invocation without it exits with status 2.
|
|
76
|
+
|
|
77
|
+
## CLI
|
|
78
|
+
|
|
79
|
+
Global configuration: `--rpc`, `--token`, `--key`, and `--json` have `ARX_RPC`, `ARX_TOKEN`, and `ARX_KEY` environment equivalents.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
arx keys new
|
|
83
|
+
arx keys import --file arxium-devnet-key-abc.json
|
|
84
|
+
printf '%s' "$SEED_HEX" | arx keys import
|
|
85
|
+
arx keys show --key arxium-devnet-key-abc.json # no passphrase needed
|
|
86
|
+
|
|
87
|
+
arx sign transfer arx1recipient 1500000000 --key key.json # nonce fetched from --rpc
|
|
88
|
+
arx sign transfer arx1recipient 1500000000 --key key.json --nonce 7 # fully offline
|
|
89
|
+
arx submit signed-action.json
|
|
90
|
+
arx send transfer arx1recipient 1500000000 --key key.json
|
|
91
|
+
|
|
92
|
+
arx query status
|
|
93
|
+
arx query account arx1address
|
|
94
|
+
arx query block 42
|
|
95
|
+
arx query block 0xblockhash
|
|
96
|
+
arx query action signature
|
|
97
|
+
arx query validators
|
|
98
|
+
arx query finality
|
|
99
|
+
arx query search arx1address
|
|
100
|
+
arx verify signature
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Supported signed actions are `transfer`, `stake`, `unstake`, `join-validator`, `leave-validator`, `register-bls-key`, `authorize-operator`, `revoke-operator`, `grant-attestation`, `revoke-attestation`, `register-asset`, `issue-asset`, `transfer-asset`, `apply-attestor`, `block-attestor`, `propose-add-attestor`, `propose-remove-attestor`, `propose-unblock-attestor`, `vote` and `execute-proposal`. Amount arguments are raw base-unit integers (IUM for ARX, the asset's smallest unit for an asset).
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
arx send apply-attestor "Arxium Bank" <owner>,<owner>,<owner> 2 <evidence-hash> <evidence-uri> # sender is the 2-of-N multisig; validators vote
|
|
107
|
+
arx send propose-remove-attestor <attestor> "reason" # validators only; blocks that attestor's grants while the vote runs
|
|
108
|
+
arx send vote <proposal-id> yes # then, after the window: arx send execute-proposal <proposal-id>
|
|
109
|
+
arx send block-attestor # attestor emergency stop; only a governance vote lifts it
|
|
110
|
+
arx send register-asset fund-a FUNDA "Fund A" 0 kyc CH,LI # <asset-id> <symbol> <name> <decimals> [topics] [jurisdictions]
|
|
111
|
+
arx send issue-asset arxasset1... 1000
|
|
112
|
+
arx send transfer-asset arxasset1... arx1recipient 100
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Builder docs (devnet quickstart, RWA walkthrough, arx-verify): https://arxium.network/docs
|
|
116
|
+
# Programmable-account tooling (0.1.5)
|
|
117
|
+
|
|
118
|
+
The SDK exports bounded account-policy codecs and stateful owner/session/guardian
|
|
119
|
+
witness helpers. `arx multisig build|sign|combine|link` provides an individual
|
|
120
|
+
member signing workflow for both immutable and stateful account addresses.
|
|
121
|
+
Historical `verifySignedAction` calls require the proven pre-action policy for
|
|
122
|
+
stateful witnesses; `arx verify SIGNATURE --policy FILE` accepts that policy.
|
|
123
|
+
|
|
124
|
+
See [the programmable-account guide](../../docs/Programmable_Accounts.md) for
|
|
125
|
+
configuration shapes, fees, block-height windows, recovery and reset requirements.
|
|
126
|
+
|
|
127
|
+
## Release validation
|
|
128
|
+
|
|
129
|
+
CI builds and installs the packed SDK on pull requests. Run `npm ci`,
|
|
130
|
+
`npm run test:release`, `npm test`, then `npm run check:package` locally.
|
|
131
|
+
The package check installs the tarball in a temporary directory, imports its
|
|
132
|
+
public entry point and checks the CLI version; it makes no chain calls.
|
|
133
|
+
|
|
134
|
+
Publication runs only for an approved `sdk-vX.Y.Z` tag matching package.json.
|
|
135
|
+
The publish job builds its own checkout, checks the packed files and smoke-tests
|
|
136
|
+
the tarball, then publishes that exact tarball with provenance. Existing npm
|
|
137
|
+
versions cannot be replaced. npm trusted-publisher configuration and release
|
|
138
|
+
authorization are still required; a passing package check is not publication
|
|
139
|
+
or on-chain compatibility proof.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { POLICY_VARIANT } from "./policy.js";
|
|
2
|
+
/** These indices are ActionPayload's positional bincode discriminants. Never derive them. */
|
|
3
|
+
export declare const ACTION_VARIANT: {
|
|
4
|
+
readonly transfer: 0;
|
|
5
|
+
readonly joinValidator: 1;
|
|
6
|
+
readonly leaveValidator: 2;
|
|
7
|
+
readonly stake: 3;
|
|
8
|
+
readonly unstake: 4;
|
|
9
|
+
readonly registerBlsKey: 6;
|
|
10
|
+
readonly authorizeOperator: 8;
|
|
11
|
+
readonly revokeOperator: 9;
|
|
12
|
+
readonly grantAttestation: 10;
|
|
13
|
+
readonly revokeAttestation: 11;
|
|
14
|
+
readonly registerAsset: 12;
|
|
15
|
+
readonly issueAsset: 13;
|
|
16
|
+
readonly transferAsset: 14;
|
|
17
|
+
readonly freezeAsset: 16;
|
|
18
|
+
readonly unfreezeAsset: 17;
|
|
19
|
+
readonly burnAsset: 18;
|
|
20
|
+
readonly setHolderFrozen: 19;
|
|
21
|
+
readonly lockHolderAmount: 20;
|
|
22
|
+
readonly unlockHolderAmount: 21;
|
|
23
|
+
readonly issuerForcedTransfer: 22;
|
|
24
|
+
readonly recoverHolder: 23;
|
|
25
|
+
readonly issueAssetTo: 24;
|
|
26
|
+
readonly lockIssuance: 25;
|
|
27
|
+
readonly transferIssuer: 26;
|
|
28
|
+
readonly setAssetMetadataUri: 27;
|
|
29
|
+
readonly setAssetLimits: 28;
|
|
30
|
+
readonly lockHolderAmountUntil: 29;
|
|
31
|
+
readonly snapshotHolders: 33;
|
|
32
|
+
readonly distributeToHolders: 34;
|
|
33
|
+
readonly redeemHolders: 35;
|
|
34
|
+
readonly splitAsset: 36;
|
|
35
|
+
readonly verifyClaimProof: 37;
|
|
36
|
+
readonly setPrivateClaims: 38;
|
|
37
|
+
readonly submitProposal: 30;
|
|
38
|
+
readonly voteProposal: 31;
|
|
39
|
+
readonly executeProposal: 32;
|
|
40
|
+
readonly applyAttestor: 41;
|
|
41
|
+
readonly blockAttestor: 42;
|
|
42
|
+
};
|
|
43
|
+
/** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
|
|
44
|
+
export declare const TOKEN_ACTION = 39;
|
|
45
|
+
export declare const TOKEN_VARIANT: {
|
|
46
|
+
readonly createToken: 0;
|
|
47
|
+
readonly mintToken: 1;
|
|
48
|
+
readonly transferToken: 2;
|
|
49
|
+
readonly burnToken: 3;
|
|
50
|
+
readonly renounceMint: 4;
|
|
51
|
+
};
|
|
52
|
+
declare const CLASS: {
|
|
53
|
+
readonly other: 0;
|
|
54
|
+
readonly real_estate: 1;
|
|
55
|
+
readonly equity: 2;
|
|
56
|
+
readonly bond: 3;
|
|
57
|
+
readonly stablecoin: 4;
|
|
58
|
+
readonly commodity: 5;
|
|
59
|
+
readonly token: 6;
|
|
60
|
+
};
|
|
61
|
+
declare const TOPIC: {
|
|
62
|
+
readonly kyc: 0;
|
|
63
|
+
readonly aml: 1;
|
|
64
|
+
readonly accredited: 2;
|
|
65
|
+
readonly jurisdiction: 3;
|
|
66
|
+
};
|
|
67
|
+
export type AssetClass = keyof typeof CLASS;
|
|
68
|
+
export type ClaimTopic = keyof typeof TOPIC;
|
|
69
|
+
export type AssetMetadata = {
|
|
70
|
+
asset_class: AssetClass;
|
|
71
|
+
decimals: number;
|
|
72
|
+
required_claims: ClaimTopic[];
|
|
73
|
+
allowed_jurisdictions: string[] | null;
|
|
74
|
+
max_supply: string | bigint | null;
|
|
75
|
+
metadata_uri: string | null;
|
|
76
|
+
symbol: string;
|
|
77
|
+
name: string;
|
|
78
|
+
};
|
|
79
|
+
export declare const ACTION_FEE = 1000000n;
|
|
80
|
+
export declare function encodeTransfer(to: string, amount: bigint): Uint8Array;
|
|
81
|
+
export declare function encodeJoinValidator(validator: string, stake: bigint, blsPubkey: Uint8Array, blsPop: Uint8Array): Uint8Array;
|
|
82
|
+
export declare function encodeLeaveValidator(validator: string): Uint8Array;
|
|
83
|
+
export declare function encodeStake(validator: string, amount: bigint): Uint8Array;
|
|
84
|
+
export declare function encodeUnstake(validator: string, amount: bigint): Uint8Array;
|
|
85
|
+
export declare function encodeRegisterBlsKey(validator: string, pubkey: Uint8Array, pop: Uint8Array): Uint8Array;
|
|
86
|
+
export declare function encodeAuthorizeOperator(operator: string): Uint8Array;
|
|
87
|
+
export declare function encodeRevokeOperator(): Uint8Array;
|
|
88
|
+
export declare function encodeGrantAttestation(subject: string, hash: string, topics: ClaimTopic[], jurisdiction: string | null): Uint8Array;
|
|
89
|
+
export declare function encodeRevokeAttestation(subject: string): Uint8Array;
|
|
90
|
+
export declare function encodeRegisterAsset(assetId: string, complianceRequired: boolean, metadata: AssetMetadata): Uint8Array;
|
|
91
|
+
export declare function encodeIssueAsset(asset: string, amount: bigint): Uint8Array;
|
|
92
|
+
export declare function encodeTransferAsset(asset: string, to: string, amount: bigint): Uint8Array;
|
|
93
|
+
export declare function encodeFreezeAsset(asset: string, frozen: boolean, reason: string): Uint8Array;
|
|
94
|
+
export declare function encodeBurnAsset(asset: string, amount: bigint): Uint8Array;
|
|
95
|
+
export declare function encodeSetHolderFrozen(asset: string, holder: string, frozen: boolean): Uint8Array;
|
|
96
|
+
export declare function encodeLockHolderAmount(asset: string, holder: string, amount: bigint, lock: boolean): Uint8Array;
|
|
97
|
+
export declare function encodeIssuerForcedTransfer(asset: string, from: string, to: string, amount: bigint, reason: string): Uint8Array;
|
|
98
|
+
export declare function encodeRecoverHolder(asset: string, lost: string, replacement: string): Uint8Array;
|
|
99
|
+
export declare function encodeIssueAssetTo(asset: string, to: string, amount: bigint): Uint8Array;
|
|
100
|
+
/** One-way: the issuer gives up minting and `max_supply` is pinned to the current supply. */
|
|
101
|
+
export declare function encodeLockIssuance(asset: string): Uint8Array;
|
|
102
|
+
/** Hands every issuer right over `asset` to `newIssuer` (key rotation, compromise remedy). */
|
|
103
|
+
export declare function encodeTransferIssuer(asset: string, newIssuer: string): Uint8Array;
|
|
104
|
+
/** `null` clears the link. */
|
|
105
|
+
export declare function encodeSetAssetMetadataUri(asset: string, metadataUri: string | null): Uint8Array;
|
|
106
|
+
/** A holding period: the lock releases itself at `untilHeight`. */
|
|
107
|
+
export declare function encodeLockHolderAmountUntil(asset: string, holder: string, amount: bigint, untilHeight: bigint): Uint8Array;
|
|
108
|
+
/** Records the record-date cap table; the corporate actions below name it by its block height. */
|
|
109
|
+
export declare function encodeSnapshotHolders(asset: string): Uint8Array;
|
|
110
|
+
/** Dividend/coupon: pays `total` of `payoutAsset` pro rata to the snapshot at `snapshotHeight`. */
|
|
111
|
+
export declare function encodeDistributeToHolders(asset: string, snapshotHeight: bigint, payoutAsset: string, total: bigint): Uint8Array;
|
|
112
|
+
/** Forces every snapshot balance back to the issuer. Send the proceeds with `encodeDistributeToHolders` first. */
|
|
113
|
+
export declare function encodeRedeemHolders(asset: string, snapshotHeight: bigint): Uint8Array;
|
|
114
|
+
/** Each snapshot holding `b` becomes `b * numerator / denominator` (split if >1, consolidation if <1). */
|
|
115
|
+
export declare function encodeSplitAsset(asset: string, snapshotHeight: bigint, numerator: bigint, denominator: bigint): Uint8Array;
|
|
116
|
+
/** Sets issuer-controlled investor, concentration, and attestation-age limits. `null` clears each limit. */
|
|
117
|
+
/** zk-KYC: a holder's private eligibility proof. `sub` is a fixed 32-byte array on the node, so it rides raw (no length prefix); `proof` is length-prefixed. */
|
|
118
|
+
export declare function encodeVerifyClaimProof(asset: string, sub: Uint8Array, todayDays: number, proof: Uint8Array): Uint8Array;
|
|
119
|
+
/** Issuer opt-in: whether holders may clear `asset`'s gate with a claim proof instead of clear-text claims. */
|
|
120
|
+
export declare function encodeSetPrivateClaims(asset: string, enabled: boolean): Uint8Array;
|
|
121
|
+
/** Permissionless token (`circuits/token`); its ref is `deriveAssetRef(sender, symbol.toLowerCase())`. `mintable: false` fixes supply at creation. Costs `token_create_fee` on top of the action fee. */
|
|
122
|
+
export declare function encodeCreateToken(symbol: string, name: string, decimals: number, initialSupply: bigint, maxSupply: bigint | null, mintable: boolean): Uint8Array;
|
|
123
|
+
export declare function encodeMintToken(tokenRef: string, to: string, amount: bigint): Uint8Array;
|
|
124
|
+
export declare function encodeTransferToken(tokenRef: string, to: string, amount: bigint): Uint8Array;
|
|
125
|
+
export declare function encodeBurnToken(tokenRef: string, amount: bigint): Uint8Array;
|
|
126
|
+
export declare function encodeRenounceMint(tokenRef: string): Uint8Array;
|
|
127
|
+
/** Applies to become an attestor. The sender is the attestor: its address must be the `threshold`-of-`owners` multisig (threshold >= 2). Pays the application bond and opens a validator vote. */
|
|
128
|
+
export declare function encodeApplyAttestor(name: string, owners: string[], threshold: number, evidenceHash: string, evidenceUri: string): Uint8Array;
|
|
129
|
+
/** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
|
|
130
|
+
export declare function encodeBlockAttestor(): Uint8Array;
|
|
131
|
+
/** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
|
|
132
|
+
export declare const GOVERNANCE_ACTION: {
|
|
133
|
+
readonly addAttestor: 4;
|
|
134
|
+
readonly removeAttestor: 5;
|
|
135
|
+
readonly unblockAttestor: 6;
|
|
136
|
+
};
|
|
137
|
+
export type GovernanceProposal = {
|
|
138
|
+
kind: "addAttestor";
|
|
139
|
+
attestor: string;
|
|
140
|
+
name: string;
|
|
141
|
+
owners: string[];
|
|
142
|
+
threshold: number;
|
|
143
|
+
evidenceHash: string;
|
|
144
|
+
evidenceUri: string;
|
|
145
|
+
} | {
|
|
146
|
+
kind: "removeAttestor" | "unblockAttestor";
|
|
147
|
+
attestor: string;
|
|
148
|
+
};
|
|
149
|
+
/** 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. */
|
|
150
|
+
export declare function encodeSubmitProposal(proposal: GovernanceProposal, description: string): Uint8Array;
|
|
151
|
+
/** Stake-weighted vote. One per validator per proposal. */
|
|
152
|
+
export declare function encodeVoteProposal(proposal: number | bigint, approve: boolean): Uint8Array;
|
|
153
|
+
/** Anyone may close a proposal once its window has ended; this applies it if it passed and always lifts a removal block. */
|
|
154
|
+
export declare function encodeExecuteProposal(proposal: number | bigint): Uint8Array;
|
|
155
|
+
export declare function encodeSetAssetLimits(asset: string, maxHolders: number | null, maxBalancePerHolder?: bigint | null, maxAttestationAge?: bigint | null): Uint8Array;
|
|
156
|
+
/** `{ name, input }` names and shapes match `fixtures/signed-actions.json`: amounts are decimal strings, byte fields are number arrays. */
|
|
157
|
+
export type ActionName = keyof typeof ACTION_VARIANT | keyof typeof TOKEN_VARIANT | keyof typeof POLICY_VARIANT;
|
|
158
|
+
export type DecodedPayload = {
|
|
159
|
+
name: ActionName;
|
|
160
|
+
input: Record<string, any>;
|
|
161
|
+
};
|
|
162
|
+
export declare function encodePayload({ name, input }: DecodedPayload): Uint8Array;
|
|
163
|
+
/**
|
|
164
|
+
* Decodes a payload the SDK can encode, then re-encodes it and throws unless the bytes match, so what
|
|
165
|
+
* a co-signer is shown is exactly what they sign. Unknown variants throw rather than display partially.
|
|
166
|
+
*/
|
|
167
|
+
export declare function decodePayload(payload: Uint8Array): DecodedPayload;
|
|
168
|
+
/** 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. */
|
|
169
|
+
export declare function signingBytes(genesis: Uint8Array, sender: string, nonce: number | bigint, payload: Uint8Array): Uint8Array;
|
|
170
|
+
export declare function signAction(privateKey: CryptoKey, genesis: Uint8Array, sender: string, nonce: number, payload: Uint8Array): Promise<string>;
|
|
171
|
+
export type SignedAction = {
|
|
172
|
+
sender: string;
|
|
173
|
+
nonce: number;
|
|
174
|
+
signature: string;
|
|
175
|
+
payload: number[];
|
|
176
|
+
};
|
|
177
|
+
export declare function submitBody(sender: string, nonce: number, signature: string, payload: Uint8Array): SignedAction;
|
|
178
|
+
/** 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. */
|
|
179
|
+
export declare const MAX_MULTISIG_MEMBERS = 16;
|
|
180
|
+
export declare function multisigAddress(threshold: number, members: Uint8Array[]): Promise<string>;
|
|
181
|
+
/** `signatures`: [member public key, that member's hex signature]. */
|
|
182
|
+
export declare function multisigSignature(threshold: number, members: Uint8Array[], signatures: [Uint8Array, string][]): string;
|
|
183
|
+
/** Same checks as `Action::verify_signature` for a multisig sender. */
|
|
184
|
+
export declare function verifyMultisig(sender: string, signature: string, message: Uint8Array): Promise<boolean>;
|
|
185
|
+
export {};
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { MULTISIG_TAG, decodeAddress, encodeAddress } from "./bech32.js";
|
|
2
|
+
import { Reader, Writer, asBuffer, fromHex, toHex } from "./bincode.js";
|
|
3
|
+
import { ACCOUNT_POLICY_ACTION, POLICY_VARIANT, encodeAccountPolicy, readAccountPolicy } from "./policy.js";
|
|
4
|
+
/** These indices are ActionPayload's positional bincode discriminants. Never derive them. */
|
|
5
|
+
export const ACTION_VARIANT = {
|
|
6
|
+
transfer: 0, joinValidator: 1, leaveValidator: 2, stake: 3, unstake: 4, registerBlsKey: 6,
|
|
7
|
+
authorizeOperator: 8, revokeOperator: 9, grantAttestation: 10, revokeAttestation: 11,
|
|
8
|
+
registerAsset: 12, issueAsset: 13, transferAsset: 14, freezeAsset: 16, unfreezeAsset: 17,
|
|
9
|
+
burnAsset: 18, setHolderFrozen: 19, lockHolderAmount: 20, unlockHolderAmount: 21,
|
|
10
|
+
issuerForcedTransfer: 22, recoverHolder: 23, issueAssetTo: 24,
|
|
11
|
+
lockIssuance: 25, transferIssuer: 26, setAssetMetadataUri: 27, setAssetLimits: 28, lockHolderAmountUntil: 29,
|
|
12
|
+
snapshotHolders: 33, distributeToHolders: 34, redeemHolders: 35, splitAsset: 36,
|
|
13
|
+
verifyClaimProof: 37, setPrivateClaims: 38,
|
|
14
|
+
submitProposal: 30, voteProposal: 31, executeProposal: 32,
|
|
15
|
+
applyAttestor: 41, blockAttestor: 42,
|
|
16
|
+
};
|
|
17
|
+
/** `ActionPayload::Token(TokenAction)` is variant 39; these are `TokenAction`'s own discriminants, written after it. */
|
|
18
|
+
export const TOKEN_ACTION = 39;
|
|
19
|
+
export const TOKEN_VARIANT = { createToken: 0, mintToken: 1, transferToken: 2, burnToken: 3, renounceMint: 4 };
|
|
20
|
+
const CLASS = { other: 0, real_estate: 1, equity: 2, bond: 3, stablecoin: 4, commodity: 5, token: 6 };
|
|
21
|
+
const TOPIC = { kyc: 0, aml: 1, accredited: 2, jurisdiction: 3 };
|
|
22
|
+
export const ACTION_FEE = 1000000n;
|
|
23
|
+
export function encodeTransfer(to, amount) { return new Writer().varint(ACTION_VARIANT.transfer).string(to).varint(amount).bytes(); }
|
|
24
|
+
export function encodeJoinValidator(validator, stake, blsPubkey, blsPop) { return new Writer().varint(ACTION_VARIANT.joinValidator).string(validator).varint(stake).vec(Array.from(blsPubkey), (w, b) => w.u8(b)).vec(Array.from(blsPop), (w, b) => w.u8(b)).bytes(); }
|
|
25
|
+
export function encodeLeaveValidator(validator) { return new Writer().varint(ACTION_VARIANT.leaveValidator).string(validator).bytes(); }
|
|
26
|
+
export function encodeStake(validator, amount) { return new Writer().varint(ACTION_VARIANT.stake).string(validator).varint(amount).bytes(); }
|
|
27
|
+
export function encodeUnstake(validator, amount) { return new Writer().varint(ACTION_VARIANT.unstake).string(validator).varint(amount).bytes(); }
|
|
28
|
+
export function encodeRegisterBlsKey(validator, pubkey, pop) { return new Writer().varint(ACTION_VARIANT.registerBlsKey).string(validator).vec(Array.from(pubkey), (w, b) => w.u8(b)).vec(Array.from(pop), (w, b) => w.u8(b)).bytes(); }
|
|
29
|
+
export function encodeAuthorizeOperator(operator) { return new Writer().varint(ACTION_VARIANT.authorizeOperator).string(operator).bytes(); }
|
|
30
|
+
export function encodeRevokeOperator() { return new Writer().varint(ACTION_VARIANT.revokeOperator).bytes(); }
|
|
31
|
+
export function encodeGrantAttestation(subject, hash, topics, jurisdiction) { return new Writer().varint(ACTION_VARIANT.grantAttestation).string(subject).string(hash).vec(topics, (w, topic) => w.varint(TOPIC[topic])).option(jurisdiction, (w, value) => w.string(value)).bytes(); }
|
|
32
|
+
export function encodeRevokeAttestation(subject) { return new Writer().varint(ACTION_VARIANT.revokeAttestation).string(subject).bytes(); }
|
|
33
|
+
export function encodeRegisterAsset(assetId, complianceRequired, metadata) { return new Writer().varint(ACTION_VARIANT.registerAsset).string(assetId).bool(complianceRequired).varint(CLASS[metadata.asset_class]).u8(metadata.decimals).vec(metadata.required_claims, (w, topic) => w.varint(TOPIC[topic])).option(metadata.allowed_jurisdictions, (w, codes) => w.vec(codes, (ww, code) => ww.string(code))).option(metadata.max_supply, (w, cap) => w.varint(BigInt(cap))).option(metadata.metadata_uri, (w, uri) => w.string(uri)).string(metadata.symbol).string(metadata.name).bytes(); }
|
|
34
|
+
export function encodeIssueAsset(asset, amount) { return new Writer().varint(ACTION_VARIANT.issueAsset).string(asset).varint(amount).bytes(); }
|
|
35
|
+
export function encodeTransferAsset(asset, to, amount) { return new Writer().varint(ACTION_VARIANT.transferAsset).string(asset).string(to).varint(amount).bytes(); }
|
|
36
|
+
export function encodeFreezeAsset(asset, frozen, reason) { return new Writer().varint(frozen ? ACTION_VARIANT.freezeAsset : ACTION_VARIANT.unfreezeAsset).string(asset).string(reason).bytes(); }
|
|
37
|
+
export function encodeBurnAsset(asset, amount) { return new Writer().varint(ACTION_VARIANT.burnAsset).string(asset).varint(amount).bytes(); }
|
|
38
|
+
export function encodeSetHolderFrozen(asset, holder, frozen) { return new Writer().varint(ACTION_VARIANT.setHolderFrozen).string(asset).string(holder).bool(frozen).bytes(); }
|
|
39
|
+
export function encodeLockHolderAmount(asset, holder, amount, lock) { return new Writer().varint(lock ? ACTION_VARIANT.lockHolderAmount : ACTION_VARIANT.unlockHolderAmount).string(asset).string(holder).varint(amount).bytes(); }
|
|
40
|
+
export function encodeIssuerForcedTransfer(asset, from, to, amount, reason) { return new Writer().varint(ACTION_VARIANT.issuerForcedTransfer).string(asset).string(from).string(to).varint(amount).string(reason).bytes(); }
|
|
41
|
+
export function encodeRecoverHolder(asset, lost, replacement) { return new Writer().varint(ACTION_VARIANT.recoverHolder).string(asset).string(lost).string(replacement).bytes(); }
|
|
42
|
+
export function encodeIssueAssetTo(asset, to, amount) { return new Writer().varint(ACTION_VARIANT.issueAssetTo).string(asset).string(to).varint(amount).bytes(); }
|
|
43
|
+
/** One-way: the issuer gives up minting and `max_supply` is pinned to the current supply. */
|
|
44
|
+
export function encodeLockIssuance(asset) { return new Writer().varint(ACTION_VARIANT.lockIssuance).string(asset).bytes(); }
|
|
45
|
+
/** Hands every issuer right over `asset` to `newIssuer` (key rotation, compromise remedy). */
|
|
46
|
+
export function encodeTransferIssuer(asset, newIssuer) { return new Writer().varint(ACTION_VARIANT.transferIssuer).string(asset).string(newIssuer).bytes(); }
|
|
47
|
+
/** `null` clears the link. */
|
|
48
|
+
export function encodeSetAssetMetadataUri(asset, metadataUri) { return new Writer().varint(ACTION_VARIANT.setAssetMetadataUri).string(asset).option(metadataUri, (w, uri) => w.string(uri)).bytes(); }
|
|
49
|
+
/** A holding period: the lock releases itself at `untilHeight`. */
|
|
50
|
+
export function encodeLockHolderAmountUntil(asset, holder, amount, untilHeight) { return new Writer().varint(ACTION_VARIANT.lockHolderAmountUntil).string(asset).string(holder).varint(amount).varint(untilHeight).bytes(); }
|
|
51
|
+
/** Records the record-date cap table; the corporate actions below name it by its block height. */
|
|
52
|
+
export function encodeSnapshotHolders(asset) { return new Writer().varint(ACTION_VARIANT.snapshotHolders).string(asset).bytes(); }
|
|
53
|
+
/** Dividend/coupon: pays `total` of `payoutAsset` pro rata to the snapshot at `snapshotHeight`. */
|
|
54
|
+
export function encodeDistributeToHolders(asset, snapshotHeight, payoutAsset, total) { return new Writer().varint(ACTION_VARIANT.distributeToHolders).string(asset).varint(snapshotHeight).string(payoutAsset).varint(total).bytes(); }
|
|
55
|
+
/** Forces every snapshot balance back to the issuer. Send the proceeds with `encodeDistributeToHolders` first. */
|
|
56
|
+
export function encodeRedeemHolders(asset, snapshotHeight) { return new Writer().varint(ACTION_VARIANT.redeemHolders).string(asset).varint(snapshotHeight).bytes(); }
|
|
57
|
+
/** Each snapshot holding `b` becomes `b * numerator / denominator` (split if >1, consolidation if <1). */
|
|
58
|
+
export function encodeSplitAsset(asset, snapshotHeight, numerator, denominator) { return new Writer().varint(ACTION_VARIANT.splitAsset).string(asset).varint(snapshotHeight).varint(numerator).varint(denominator).bytes(); }
|
|
59
|
+
/** Sets issuer-controlled investor, concentration, and attestation-age limits. `null` clears each limit. */
|
|
60
|
+
/** zk-KYC: a holder's private eligibility proof. `sub` is a fixed 32-byte array on the node, so it rides raw (no length prefix); `proof` is length-prefixed. */
|
|
61
|
+
export function encodeVerifyClaimProof(asset, sub, todayDays, proof) { if (sub.length !== 32)
|
|
62
|
+
throw new Error("sub must be 32 bytes"); return new Writer().varint(ACTION_VARIANT.verifyClaimProof).string(asset).raw(sub).varint(todayDays).vec(Array.from(proof), (w, b) => w.u8(b)).bytes(); }
|
|
63
|
+
/** Issuer opt-in: whether holders may clear `asset`'s gate with a claim proof instead of clear-text claims. */
|
|
64
|
+
export function encodeSetPrivateClaims(asset, enabled) { return new Writer().varint(ACTION_VARIANT.setPrivateClaims).string(asset).bool(enabled).bytes(); }
|
|
65
|
+
const token = (name) => new Writer().varint(TOKEN_ACTION).varint(TOKEN_VARIANT[name]);
|
|
66
|
+
/** Permissionless token (`circuits/token`); its ref is `deriveAssetRef(sender, symbol.toLowerCase())`. `mintable: false` fixes supply at creation. Costs `token_create_fee` on top of the action fee. */
|
|
67
|
+
export function encodeCreateToken(symbol, name, decimals, initialSupply, maxSupply, mintable) { return token("createToken").string(symbol).string(name).u8(decimals).varint(initialSupply).option(maxSupply, (w, cap) => w.varint(cap)).bool(mintable).bytes(); }
|
|
68
|
+
export function encodeMintToken(tokenRef, to, amount) { return token("mintToken").string(tokenRef).string(to).varint(amount).bytes(); }
|
|
69
|
+
export function encodeTransferToken(tokenRef, to, amount) { return token("transferToken").string(tokenRef).string(to).varint(amount).bytes(); }
|
|
70
|
+
export function encodeBurnToken(tokenRef, amount) { return token("burnToken").string(tokenRef).varint(amount).bytes(); }
|
|
71
|
+
export function encodeRenounceMint(tokenRef) { return token("renounceMint").string(tokenRef).bytes(); }
|
|
72
|
+
/** Applies to become an attestor. The sender is the attestor: its address must be the `threshold`-of-`owners` multisig (threshold >= 2). Pays the application bond and opens a validator vote. */
|
|
73
|
+
export function encodeApplyAttestor(name, owners, threshold, evidenceHash, evidenceUri) { return new Writer().varint(ACTION_VARIANT.applyAttestor).string(name).vec(owners, (w, o) => w.string(o)).u8(threshold).string(evidenceHash).string(evidenceUri).bytes(); }
|
|
74
|
+
/** Emergency stop: the sending attestor can no longer grant new attestations. Only a governance vote lifts it. */
|
|
75
|
+
export function encodeBlockAttestor() { return new Writer().varint(ACTION_VARIANT.blockAttestor).bytes(); }
|
|
76
|
+
/** `GovernanceAction`'s own discriminants (after `SetChainParams` 0, `TreasurySpend` 1, `ResolveDispute` 2, `ReinstateValidator` 3). Only the attestor actions are encodable here. */
|
|
77
|
+
export const GOVERNANCE_ACTION = { addAttestor: 4, removeAttestor: 5, unblockAttestor: 6 };
|
|
78
|
+
/** 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. */
|
|
79
|
+
export function encodeSubmitProposal(proposal, description) {
|
|
80
|
+
const w = new Writer().varint(ACTION_VARIANT.submitProposal).varint(GOVERNANCE_ACTION[proposal.kind]).string(proposal.attestor);
|
|
81
|
+
if (proposal.kind === "addAttestor")
|
|
82
|
+
w.string(proposal.name).vec(proposal.owners, (x, o) => x.string(o)).u8(proposal.threshold).string(proposal.evidenceHash).string(proposal.evidenceUri);
|
|
83
|
+
return w.string(description).bytes();
|
|
84
|
+
}
|
|
85
|
+
/** Stake-weighted vote. One per validator per proposal. */
|
|
86
|
+
export function encodeVoteProposal(proposal, approve) { return new Writer().varint(ACTION_VARIANT.voteProposal).varint(BigInt(proposal)).bool(approve).bytes(); }
|
|
87
|
+
/** Anyone may close a proposal once its window has ended; this applies it if it passed and always lifts a removal block. */
|
|
88
|
+
export function encodeExecuteProposal(proposal) { return new Writer().varint(ACTION_VARIANT.executeProposal).varint(BigInt(proposal)).bytes(); }
|
|
89
|
+
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(); }
|
|
90
|
+
const nullableBig = (value) => value == null ? null : BigInt(value);
|
|
91
|
+
export function encodePayload({ name, input }) {
|
|
92
|
+
if (name in POLICY_VARIANT)
|
|
93
|
+
return encodeAccountPolicy(name, input);
|
|
94
|
+
switch (name) {
|
|
95
|
+
case "transfer": return encodeTransfer(input.to, BigInt(input.amount));
|
|
96
|
+
case "joinValidator": return encodeJoinValidator(input.validator, BigInt(input.stake), Uint8Array.from(input.blsPubkey), Uint8Array.from(input.blsPop));
|
|
97
|
+
case "leaveValidator": return encodeLeaveValidator(input.validator);
|
|
98
|
+
case "stake": return encodeStake(input.validator, BigInt(input.amount));
|
|
99
|
+
case "unstake": return encodeUnstake(input.validator, BigInt(input.amount));
|
|
100
|
+
case "registerBlsKey": return encodeRegisterBlsKey(input.validator, Uint8Array.from(input.pubkey), Uint8Array.from(input.pop));
|
|
101
|
+
case "authorizeOperator": return encodeAuthorizeOperator(input.operator);
|
|
102
|
+
case "revokeOperator": return encodeRevokeOperator();
|
|
103
|
+
case "grantAttestation": return encodeGrantAttestation(input.subject, input.hash, input.topics, input.jurisdiction);
|
|
104
|
+
case "revokeAttestation": return encodeRevokeAttestation(input.subject);
|
|
105
|
+
case "registerAsset": return encodeRegisterAsset(input.assetId, input.complianceRequired, input.metadata);
|
|
106
|
+
case "issueAsset": return encodeIssueAsset(input.asset, BigInt(input.amount));
|
|
107
|
+
case "transferAsset": return encodeTransferAsset(input.asset, input.to, BigInt(input.amount));
|
|
108
|
+
case "freezeAsset":
|
|
109
|
+
case "unfreezeAsset": return encodeFreezeAsset(input.asset, input.frozen, input.reason);
|
|
110
|
+
case "burnAsset": return encodeBurnAsset(input.asset, BigInt(input.amount));
|
|
111
|
+
case "setHolderFrozen": return encodeSetHolderFrozen(input.asset, input.holder, input.frozen);
|
|
112
|
+
case "lockHolderAmount":
|
|
113
|
+
case "unlockHolderAmount": return encodeLockHolderAmount(input.asset, input.holder, BigInt(input.amount), input.lock);
|
|
114
|
+
case "issuerForcedTransfer": return encodeIssuerForcedTransfer(input.asset, input.from, input.to, BigInt(input.amount), input.reason);
|
|
115
|
+
case "recoverHolder": return encodeRecoverHolder(input.asset, input.lost, input.replacement);
|
|
116
|
+
case "issueAssetTo": return encodeIssueAssetTo(input.asset, input.to, BigInt(input.amount));
|
|
117
|
+
case "lockIssuance": return encodeLockIssuance(input.asset);
|
|
118
|
+
case "transferIssuer": return encodeTransferIssuer(input.asset, input.newIssuer);
|
|
119
|
+
case "setAssetMetadataUri": return encodeSetAssetMetadataUri(input.asset, input.metadataUri);
|
|
120
|
+
case "lockHolderAmountUntil": return encodeLockHolderAmountUntil(input.asset, input.holder, BigInt(input.amount), BigInt(input.untilHeight));
|
|
121
|
+
case "snapshotHolders": return encodeSnapshotHolders(input.asset);
|
|
122
|
+
case "distributeToHolders": return encodeDistributeToHolders(input.asset, BigInt(input.snapshotHeight), input.payoutAsset, BigInt(input.total));
|
|
123
|
+
case "redeemHolders": return encodeRedeemHolders(input.asset, BigInt(input.snapshotHeight));
|
|
124
|
+
case "splitAsset": return encodeSplitAsset(input.asset, BigInt(input.snapshotHeight), BigInt(input.numerator), BigInt(input.denominator));
|
|
125
|
+
case "setAssetLimits": return encodeSetAssetLimits(input.asset, input.maxHolders, nullableBig(input.maxBalancePerHolder), nullableBig(input.maxAttestationAge));
|
|
126
|
+
case "verifyClaimProof": return encodeVerifyClaimProof(input.asset, Uint8Array.from(input.sub), input.todayDays, Uint8Array.from(input.proof));
|
|
127
|
+
case "setPrivateClaims": return encodeSetPrivateClaims(input.asset, input.enabled);
|
|
128
|
+
case "submitProposal": return encodeSubmitProposal(input.action, input.description);
|
|
129
|
+
case "voteProposal": return encodeVoteProposal(BigInt(input.proposal), input.approve);
|
|
130
|
+
case "executeProposal": return encodeExecuteProposal(BigInt(input.proposal));
|
|
131
|
+
case "applyAttestor": return encodeApplyAttestor(input.name, input.owners, input.threshold, input.evidenceHash, input.evidenceUri);
|
|
132
|
+
case "blockAttestor": return encodeBlockAttestor();
|
|
133
|
+
case "createToken": return encodeCreateToken(input.symbol, input.name, input.decimals, BigInt(input.initialSupply), nullableBig(input.maxSupply), input.mintable);
|
|
134
|
+
case "mintToken": return encodeMintToken(input.token, input.to, BigInt(input.amount));
|
|
135
|
+
case "transferToken": return encodeTransferToken(input.token, input.to, BigInt(input.amount));
|
|
136
|
+
case "burnToken": return encodeBurnToken(input.token, BigInt(input.amount));
|
|
137
|
+
case "renounceMint": return encodeRenounceMint(input.token);
|
|
138
|
+
default: throw new Error(`unsupported action: ${name}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const invert = (table) => Object.fromEntries(Object.entries(table).map(([key, value]) => [value, key]));
|
|
142
|
+
const VARIANT_NAME = invert(ACTION_VARIANT), TOKEN_NAME = invert(TOKEN_VARIANT), CLASS_NAME = invert(CLASS), TOPIC_NAME = invert(TOPIC), GOVERNANCE_NAME = invert(GOVERNANCE_ACTION);
|
|
143
|
+
const known = (value, what) => { if (value === undefined)
|
|
144
|
+
throw new Error(`unknown ${what}`); return value; };
|
|
145
|
+
/**
|
|
146
|
+
* Decodes a payload the SDK can encode, then re-encodes it and throws unless the bytes match, so what
|
|
147
|
+
* a co-signer is shown is exactly what they sign. Unknown variants throw rather than display partially.
|
|
148
|
+
*/
|
|
149
|
+
export function decodePayload(payload) {
|
|
150
|
+
const r = new Reader(payload), outer = Number(r.varint());
|
|
151
|
+
if (outer === ACCOUNT_POLICY_ACTION) {
|
|
152
|
+
const decoded = readAccountPolicy(r);
|
|
153
|
+
r.done();
|
|
154
|
+
if (toHex(encodePayload(decoded)) !== toHex(payload))
|
|
155
|
+
throw new Error("payload is not in canonical form");
|
|
156
|
+
return decoded;
|
|
157
|
+
}
|
|
158
|
+
const name = outer === TOKEN_ACTION ? known(TOKEN_NAME[Number(r.varint())], "token action") : known(VARIANT_NAME[outer], "action variant");
|
|
159
|
+
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"));
|
|
160
|
+
const read = {
|
|
161
|
+
transfer: () => ({ to: str(), amount: amount() }),
|
|
162
|
+
joinValidator: () => ({ validator: str(), stake: amount(), blsPubkey: bytes(), blsPop: bytes() }),
|
|
163
|
+
leaveValidator: () => ({ validator: str() }),
|
|
164
|
+
stake: () => ({ validator: str(), amount: amount() }),
|
|
165
|
+
unstake: () => ({ validator: str(), amount: amount() }),
|
|
166
|
+
registerBlsKey: () => ({ validator: str(), pubkey: bytes(), pop: bytes() }),
|
|
167
|
+
authorizeOperator: () => ({ operator: str() }),
|
|
168
|
+
revokeOperator: () => ({}),
|
|
169
|
+
grantAttestation: () => ({ subject: str(), hash: str(), topics: topics(), jurisdiction: r.option(str) }),
|
|
170
|
+
revokeAttestation: () => ({ subject: str() }),
|
|
171
|
+
registerAsset: () => ({ assetId: str(), complianceRequired: r.bool(), metadata: { asset_class: known(CLASS_NAME[Number(r.varint())], "asset class"), decimals: r.u8(), required_claims: topics(), allowed_jurisdictions: r.option((rr) => rr.vec(str)), max_supply: r.option(amount), metadata_uri: r.option(str), symbol: str(), name: str() } }),
|
|
172
|
+
issueAsset: () => ({ asset: str(), amount: amount() }),
|
|
173
|
+
transferAsset: () => ({ asset: str(), to: str(), amount: amount() }),
|
|
174
|
+
freezeAsset: () => ({ asset: str(), frozen: true, reason: str() }),
|
|
175
|
+
unfreezeAsset: () => ({ asset: str(), frozen: false, reason: str() }),
|
|
176
|
+
burnAsset: () => ({ asset: str(), amount: amount() }),
|
|
177
|
+
setHolderFrozen: () => ({ asset: str(), holder: str(), frozen: r.bool() }),
|
|
178
|
+
lockHolderAmount: () => ({ asset: str(), holder: str(), amount: amount(), lock: true }),
|
|
179
|
+
unlockHolderAmount: () => ({ asset: str(), holder: str(), amount: amount(), lock: false }),
|
|
180
|
+
issuerForcedTransfer: () => ({ asset: str(), from: str(), to: str(), amount: amount(), reason: str() }),
|
|
181
|
+
recoverHolder: () => ({ asset: str(), lost: str(), replacement: str() }),
|
|
182
|
+
issueAssetTo: () => ({ asset: str(), to: str(), amount: amount() }),
|
|
183
|
+
lockIssuance: () => ({ asset: str() }),
|
|
184
|
+
transferIssuer: () => ({ asset: str(), newIssuer: str() }),
|
|
185
|
+
setAssetMetadataUri: () => ({ asset: str(), metadataUri: r.option(str) }),
|
|
186
|
+
lockHolderAmountUntil: () => ({ asset: str(), holder: str(), amount: amount(), untilHeight: amount() }),
|
|
187
|
+
snapshotHolders: () => ({ asset: str() }),
|
|
188
|
+
distributeToHolders: () => ({ asset: str(), snapshotHeight: amount(), payoutAsset: str(), total: amount() }),
|
|
189
|
+
redeemHolders: () => ({ asset: str(), snapshotHeight: amount() }),
|
|
190
|
+
splitAsset: () => ({ asset: str(), snapshotHeight: amount(), numerator: amount(), denominator: amount() }),
|
|
191
|
+
setAssetLimits: () => ({ asset: str(), maxHolders: r.option((rr) => Number(rr.varint())), maxBalancePerHolder: r.option(amount), maxAttestationAge: r.option(amount) }),
|
|
192
|
+
verifyClaimProof: () => ({ asset: str(), sub: Array.from({ length: 32 }, () => r.u8()), todayDays: Number(r.varint()), proof: bytes() }),
|
|
193
|
+
setPrivateClaims: () => ({ asset: str(), enabled: r.bool() }),
|
|
194
|
+
submitProposal: () => { const kind = known(GOVERNANCE_NAME[Number(r.varint())], "governance action"), attestor = str(); const action = kind === "addAttestor" ? { kind, attestor, name: str(), owners: r.vec((rr) => rr.string()), threshold: r.u8(), evidenceHash: str(), evidenceUri: str() } : { kind, attestor }; return { action, description: str() }; },
|
|
195
|
+
voteProposal: () => ({ proposal: amount(), approve: r.bool() }),
|
|
196
|
+
executeProposal: () => ({ proposal: amount() }),
|
|
197
|
+
applyAttestor: () => ({ name: str(), owners: r.vec((rr) => rr.string()), threshold: r.u8(), evidenceHash: str(), evidenceUri: str() }),
|
|
198
|
+
blockAttestor: () => ({}),
|
|
199
|
+
createToken: () => ({ symbol: str(), name: str(), decimals: r.u8(), initialSupply: amount(), maxSupply: r.option(amount), mintable: r.bool() }),
|
|
200
|
+
mintToken: () => ({ token: str(), to: str(), amount: amount() }),
|
|
201
|
+
transferToken: () => ({ token: str(), to: str(), amount: amount() }),
|
|
202
|
+
burnToken: () => ({ token: str(), amount: amount() }),
|
|
203
|
+
renounceMint: () => ({ token: str() }),
|
|
204
|
+
};
|
|
205
|
+
const decoded = { name, input: read[name]() };
|
|
206
|
+
r.done();
|
|
207
|
+
if (toHex(encodePayload(decoded)) !== toHex(payload))
|
|
208
|
+
throw new Error("payload is not in canonical form");
|
|
209
|
+
return decoded;
|
|
210
|
+
}
|
|
211
|
+
/** 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. */
|
|
212
|
+
export function signingBytes(genesis, sender, nonce, payload) { if (genesis.length !== 32)
|
|
213
|
+
throw new Error("genesis hash must be 32 bytes"); return new Writer().raw(genesis).string(sender).varint(nonce).raw(payload).bytes(); }
|
|
214
|
+
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))))); }
|
|
215
|
+
export function submitBody(sender, nonce, signature, payload) { return { sender, nonce, signature, payload: Array.from(payload) }; }
|
|
216
|
+
/** 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. */
|
|
217
|
+
export const MAX_MULTISIG_MEMBERS = 16;
|
|
218
|
+
const compareBytes = (a, b) => { for (let i = 0; i < a.length; i++)
|
|
219
|
+
if (a[i] !== b[i])
|
|
220
|
+
return a[i] - b[i]; return 0; };
|
|
221
|
+
function multisigPolicy(threshold, members) { const sorted = [...members].sort(compareBytes); if (!sorted.length || sorted.length > MAX_MULTISIG_MEMBERS)
|
|
222
|
+
throw new Error("member count must be 1..=16"); if (!Number.isInteger(threshold) || threshold < 1 || threshold > sorted.length)
|
|
223
|
+
throw new Error("threshold must be 1..=members"); if (sorted.some((m, i) => m.length !== 32 || (i > 0 && compareBytes(sorted[i - 1], m) >= 0)))
|
|
224
|
+
throw new Error("members must be unique 32-byte keys"); return sorted; }
|
|
225
|
+
async function policyHash(threshold, sorted) { const domain = new TextEncoder().encode("arxium-multisig-v1"), input = new Uint8Array(domain.length + 2 + 32 * sorted.length); input.set(domain); input.set([threshold, sorted.length], domain.length); sorted.forEach((m, i) => input.set(m, domain.length + 2 + 32 * i)); return new Uint8Array(await crypto.subtle.digest("SHA-256", input)); }
|
|
226
|
+
export async function multisigAddress(threshold, members) { const hash = await policyHash(threshold, multisigPolicy(threshold, members)); return encodeAddress(Uint8Array.from([MULTISIG_TAG, ...hash])); }
|
|
227
|
+
/** `signatures`: [member public key, that member's hex signature]. */
|
|
228
|
+
export function multisigSignature(threshold, members, signatures) { const sorted = multisigPolicy(threshold, members); const indexed = signatures.map(([key, sig]) => { const index = sorted.findIndex((m) => compareBytes(m, key) === 0); if (index < 0)
|
|
229
|
+
throw new Error("signer is not a member"); return [index, fromHex(sig)]; }).sort((a, b) => a[0] - b[0]); const out = new Writer().raw(Uint8Array.from([threshold, sorted.length])); sorted.forEach((m) => out.raw(m)); indexed.forEach(([index, sig]) => out.raw(Uint8Array.from([index])).raw(sig)); return toHex(out.bytes()); }
|
|
230
|
+
/** Same checks as `Action::verify_signature` for a multisig sender. */
|
|
231
|
+
export async function verifyMultisig(sender, signature, message) { const sent = decodeAddress(sender), witness = fromHex(signature), [threshold, n] = witness; if (sent.length !== 33 || witness.length !== 2 + 32 * n + 65 * threshold)
|
|
232
|
+
return false; const members = Array.from({ length: n }, (_, i) => witness.slice(2 + 32 * i, 34 + 32 * i)); const hash = await policyHash(threshold, multisigPolicy(threshold, members)); if (compareBytes(hash, sent.slice(1)) !== 0)
|
|
233
|
+
return false; let last = -1; for (let at = 2 + 32 * n; at < witness.length; at += 65) {
|
|
234
|
+
const index = witness[at];
|
|
235
|
+
if (index >= n || index <= last)
|
|
236
|
+
return false;
|
|
237
|
+
last = index;
|
|
238
|
+
const key = await crypto.subtle.importKey("raw", asBuffer(members[index]), "Ed25519", false, ["verify"]);
|
|
239
|
+
if (!await crypto.subtle.verify("Ed25519", key, asBuffer(witness.slice(at + 1, at + 65)), asBuffer(message)))
|
|
240
|
+
return false;
|
|
241
|
+
} return true; }
|
package/dist/amounts.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const IUM_PER_ARX = 1000000000n;
|
|
2
|
+
export function arxToIum(value) { if (!/^(0|[1-9]\d*)(\.\d{1,9})?$/.test(value))
|
|
3
|
+
throw new Error("amount must be a non-negative decimal with at most 9 places"); const [whole, fraction = ""] = value.split("."); return BigInt(whole) * IUM_PER_ARX + BigInt((fraction + "000000000").slice(0, 9)); }
|
|
4
|
+
export function iumToArx(value) { if (value < 0n)
|
|
5
|
+
throw new RangeError("amount cannot be negative"); const whole = value / IUM_PER_ARX, fraction = (value % IUM_PER_ARX).toString().padStart(9, "0").replace(/0+$/, ""); return fraction ? `${whole}.${fraction}` : whole.toString(); }
|
package/dist/bech32.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const HRP = "arx";
|
|
2
|
+
export declare const ASSET_HRP = "arxasset";
|
|
3
|
+
/** A plain address is a 32-byte ed25519 key; a multisig one is 0x01 ‖ sha256(policy), see multisig.ts. */
|
|
4
|
+
export declare const MULTISIG_TAG = 1;
|
|
5
|
+
export declare function encodeAddress(publicKey: Uint8Array): string;
|
|
6
|
+
export declare function decodeAddress(address: string): Uint8Array;
|
|
7
|
+
export declare function isMultisigAddress(address: string): boolean;
|
|
8
|
+
export declare function decodeAssetRef(ref: string): Uint8Array;
|
|
9
|
+
export declare function deriveAssetRef(issuer: string, assetId: string): Promise<string>;
|