@blockula/sdk 0.1.0 → 0.3.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 +6 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +99 -3
- package/dist/passkey.d.ts +2 -0
- package/dist/passkey.js +10 -5
- package/dist/tx.d.ts +64 -0
- package/dist/tx.js +68 -0
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -27,4 +27,10 @@ const wallet = await createPasskeyWallet({ username: "vlad" });
|
|
|
27
27
|
|
|
28
28
|
Amounts are FANG strings (`"1.5"`) or bigint drops (1 FANG = 10⁹ drops). Every transaction result includes a plain-English `summary`.
|
|
29
29
|
|
|
30
|
+
- `send("alice.boo", …)` resolves the name first and signs the **address**, so whoever you checked is exactly who gets paid.
|
|
31
|
+
- Node errors throw `ApiError` (`.status`: 429 means rate limited). A transaction that fails at execution throws `TxFailedError` from `waitForTx`.
|
|
32
|
+
- Pass `rpId` to `createPasskeyWallet`/`unlockPasskeyWallet` to pin wallets to your production domain. Passkeys are bound to that hostname for good.
|
|
33
|
+
|
|
34
|
+
The public testnet is `https://blockula-testnet.fly.dev`, and the wallet is https://blockula-wallet.vercel.app.
|
|
35
|
+
|
|
30
36
|
⚠️ Early and unaudited: test chains only.
|
package/dist/index.d.ts
CHANGED
|
@@ -55,10 +55,56 @@ export declare class Wallet {
|
|
|
55
55
|
expiresInBlocks: number;
|
|
56
56
|
}): Promise<any>;
|
|
57
57
|
removeKey(keyIdHex: string): Promise<any>;
|
|
58
|
+
/**
|
|
59
|
+
* Offer a validator: bonds `bond` FANG from this account. `consensusKeyHex` is the validator
|
|
60
|
+
* node's ML-DSA-65 public key (see `blockula validator init`). Pending until an admin approves.
|
|
61
|
+
*/
|
|
62
|
+
registerValidator(opts: {
|
|
63
|
+
consensusKey: string;
|
|
64
|
+
p2pAddr: string;
|
|
65
|
+
httpAddr: string;
|
|
66
|
+
bond: string | number | bigint;
|
|
67
|
+
pop: string;
|
|
68
|
+
}): Promise<any>;
|
|
69
|
+
/** Season 0 admins only. */
|
|
70
|
+
approveValidator(validator: string): Promise<any>;
|
|
71
|
+
/** Season 0 admins only. */
|
|
72
|
+
removeValidator(validator: string): Promise<any>;
|
|
73
|
+
/** Claim a GitHub handle. Verify it by creating a public gist (as that user) containing this account's address. */
|
|
74
|
+
linkGithub(handle: string, gistId: string): Promise<any>;
|
|
75
|
+
unlinkGithub(handle: string): Promise<any>;
|
|
76
|
+
/** Put a bounty on a GitHub issue. `expiresInBlocks` from now (max ~1 year). */
|
|
77
|
+
createHunt(opts: {
|
|
78
|
+
repo: string;
|
|
79
|
+
issue: number;
|
|
80
|
+
amount: string | number | bigint;
|
|
81
|
+
title: string;
|
|
82
|
+
expiresInBlocks: number;
|
|
83
|
+
}): Promise<any>;
|
|
84
|
+
fundHunt(huntId: number, amount: string | number | bigint): Promise<any>;
|
|
85
|
+
/** Pay a hunt's bounty (creator or admin). `to` may be a .boo name; it's resolved and the address is signed. */
|
|
86
|
+
awardHunt(huntId: number, to: string, pr: number, familiarKey?: string): Promise<any>;
|
|
87
|
+
cancelHunt(huntId: number): Promise<any>;
|
|
88
|
+
/** Mark one of this account's session keys (an agent) as a Familiar. */
|
|
89
|
+
registerFamiliar(keyIdHex: string, label: string): Promise<any>;
|
|
90
|
+
/** Reclaim the bond of a removed validator this account owns. */
|
|
91
|
+
unbond(validator: string): Promise<any>;
|
|
58
92
|
balance(): Promise<string>;
|
|
59
93
|
/** Dev/test chains only: drip 100 FANG to this wallet. */
|
|
60
94
|
faucet(wait?: boolean): Promise<any>;
|
|
61
95
|
}
|
|
96
|
+
/** An error response from a Blockula node. `status` is the HTTP status (429 = rate limited). */
|
|
97
|
+
export declare class ApiError extends Error {
|
|
98
|
+
readonly status: number;
|
|
99
|
+
constructor(status: number, message: string);
|
|
100
|
+
}
|
|
101
|
+
/** A transaction that was rejected at execution (fee charged) or dropped from the mempool. */
|
|
102
|
+
export declare class TxFailedError extends Error {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly reason: string;
|
|
105
|
+
readonly tx: any;
|
|
106
|
+
constructor(id: string, reason: string, tx: any);
|
|
107
|
+
}
|
|
62
108
|
export declare class Client {
|
|
63
109
|
readonly rpc: string;
|
|
64
110
|
constructor(rpc?: string);
|
|
@@ -72,6 +118,13 @@ export declare class Client {
|
|
|
72
118
|
/** Accounts a key is authorized on (its own address, plus any it was added to). */
|
|
73
119
|
keyAccounts(keyIdHex: string): Promise<any>;
|
|
74
120
|
search(q: string): Promise<any>;
|
|
121
|
+
/** Active set for the current epoch, pending/approved validators, next-epoch set, epoch timing. */
|
|
122
|
+
validators(): Promise<any>;
|
|
123
|
+
/** Hunts (bounties on GitHub issues), biggest first. */
|
|
124
|
+
hunts(status?: "open" | "awarded" | "cancelled" | "expired"): Promise<any>;
|
|
125
|
+
hunt(id: number): Promise<any>;
|
|
126
|
+
/** Off-chain check of a GitHub link (gist owned by the handle containing the address). */
|
|
127
|
+
github(handle: string): Promise<any>;
|
|
75
128
|
resolve(nameOrAddress: string): Promise<string>;
|
|
76
129
|
submit(tx: Tx, wait?: boolean): Promise<any>;
|
|
77
130
|
faucet(to: string, wait?: boolean): Promise<any>;
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,9 @@ export function keyId(publicKey) {
|
|
|
13
13
|
return bytesToHex(blake3(publicKey));
|
|
14
14
|
}
|
|
15
15
|
export function isAddress(s) {
|
|
16
|
+
// Addresses are canonical lowercase; the node rejects any other casing.
|
|
17
|
+
if (s !== s.toLowerCase())
|
|
18
|
+
return false;
|
|
16
19
|
try {
|
|
17
20
|
const { prefix, words } = bech32m.decode(s);
|
|
18
21
|
return prefix === "boo" && bech32m.fromWords(words).length === 32;
|
|
@@ -85,7 +88,10 @@ export class Wallet {
|
|
|
85
88
|
}
|
|
86
89
|
/** Send FANG to a `.boo` name or `boo1…` address. `amount` is in FANG ("1.5") or bigint drops. */
|
|
87
90
|
async send(to, amount, opts = {}) {
|
|
88
|
-
|
|
91
|
+
// Sign the resolved address, not the name: what you checked is exactly who gets paid,
|
|
92
|
+
// even if the name changes hands before the block is sealed.
|
|
93
|
+
const recipient = await this.client.resolve(to);
|
|
94
|
+
const tx = await this.build({ type: "transfer", to: recipient, amount: parseFang(amount).toString() }, opts.memo);
|
|
89
95
|
return this.client.submit(tx, opts.wait ?? true);
|
|
90
96
|
}
|
|
91
97
|
async registerName(name, opts = {}) {
|
|
@@ -101,6 +107,10 @@ export class Wallet {
|
|
|
101
107
|
* Example: `addSessionKey(agentKeyId, "claude", { limit: "10", periodBlocks: 86_400, expiresInBlocks: 30 * 86_400 })`.
|
|
102
108
|
*/
|
|
103
109
|
async addSessionKey(keyIdHex, label, opts) {
|
|
110
|
+
if (!Number.isSafeInteger(opts.periodBlocks) || opts.periodBlocks < 1)
|
|
111
|
+
throw new Error("periodBlocks must be a positive whole number of blocks");
|
|
112
|
+
if (!Number.isSafeInteger(opts.expiresInBlocks) || opts.expiresInBlocks < 1)
|
|
113
|
+
throw new Error("expiresInBlocks must be a positive whole number of blocks");
|
|
104
114
|
const { height } = await this.client.status();
|
|
105
115
|
const role = { type: "session", limit: parseFang(opts.limit).toString(), period_blocks: opts.periodBlocks, expires_at: height + opts.expiresInBlocks };
|
|
106
116
|
return this.client.submit(await this.build({ type: "add_key", key_id: keyIdHex, label, role }));
|
|
@@ -108,6 +118,52 @@ export class Wallet {
|
|
|
108
118
|
async removeKey(keyIdHex) {
|
|
109
119
|
return this.client.submit(await this.build({ type: "remove_key", key_id: keyIdHex }));
|
|
110
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Offer a validator: bonds `bond` FANG from this account. `consensusKeyHex` is the validator
|
|
123
|
+
* node's ML-DSA-65 public key (see `blockula validator init`). Pending until an admin approves.
|
|
124
|
+
*/
|
|
125
|
+
async registerValidator(opts) {
|
|
126
|
+
return this.client.submit(await this.build({ type: "register_validator", consensus_key: opts.consensusKey, p2p_addr: opts.p2pAddr, http_addr: opts.httpAddr, bond: parseFang(opts.bond).toString(), pop: opts.pop }));
|
|
127
|
+
}
|
|
128
|
+
/** Season 0 admins only. */
|
|
129
|
+
async approveValidator(validator) {
|
|
130
|
+
return this.client.submit(await this.build({ type: "approve_validator", validator }));
|
|
131
|
+
}
|
|
132
|
+
/** Season 0 admins only. */
|
|
133
|
+
async removeValidator(validator) {
|
|
134
|
+
return this.client.submit(await this.build({ type: "remove_validator", validator }));
|
|
135
|
+
}
|
|
136
|
+
/** Claim a GitHub handle. Verify it by creating a public gist (as that user) containing this account's address. */
|
|
137
|
+
async linkGithub(handle, gistId) {
|
|
138
|
+
return this.client.submit(await this.build({ type: "link_github", handle, gist_id: gistId }));
|
|
139
|
+
}
|
|
140
|
+
async unlinkGithub(handle) {
|
|
141
|
+
return this.client.submit(await this.build({ type: "unlink_github", handle }));
|
|
142
|
+
}
|
|
143
|
+
/** Put a bounty on a GitHub issue. `expiresInBlocks` from now (max ~1 year). */
|
|
144
|
+
async createHunt(opts) {
|
|
145
|
+
const { height } = await this.client.status();
|
|
146
|
+
return this.client.submit(await this.build({ type: "create_hunt", repo: opts.repo, issue: opts.issue, amount: parseFang(opts.amount).toString(), expires_at: height + opts.expiresInBlocks, title: opts.title }));
|
|
147
|
+
}
|
|
148
|
+
async fundHunt(huntId, amount) {
|
|
149
|
+
return this.client.submit(await this.build({ type: "fund_hunt", hunt_id: huntId, amount: parseFang(amount).toString() }));
|
|
150
|
+
}
|
|
151
|
+
/** Pay a hunt's bounty (creator or admin). `to` may be a .boo name; it's resolved and the address is signed. */
|
|
152
|
+
async awardHunt(huntId, to, pr, familiarKey) {
|
|
153
|
+
const addr = await this.client.resolve(to);
|
|
154
|
+
return this.client.submit(await this.build({ type: "award_hunt", hunt_id: huntId, to: addr, pr, ...(familiarKey ? { familiar_key: familiarKey } : {}) }));
|
|
155
|
+
}
|
|
156
|
+
async cancelHunt(huntId) {
|
|
157
|
+
return this.client.submit(await this.build({ type: "cancel_hunt", hunt_id: huntId }));
|
|
158
|
+
}
|
|
159
|
+
/** Mark one of this account's session keys (an agent) as a Familiar. */
|
|
160
|
+
async registerFamiliar(keyIdHex, label) {
|
|
161
|
+
return this.client.submit(await this.build({ type: "register_familiar", key_id: keyIdHex, label }));
|
|
162
|
+
}
|
|
163
|
+
/** Reclaim the bond of a removed validator this account owns. */
|
|
164
|
+
async unbond(validator) {
|
|
165
|
+
return this.client.submit(await this.build({ type: "unbond", validator }));
|
|
166
|
+
}
|
|
111
167
|
balance() {
|
|
112
168
|
return this.client.account(this.account).then((a) => a.balance_display, () => "0 FANG");
|
|
113
169
|
}
|
|
@@ -116,6 +172,28 @@ export class Wallet {
|
|
|
116
172
|
return this.client.faucet(this.account, wait);
|
|
117
173
|
}
|
|
118
174
|
}
|
|
175
|
+
/** An error response from a Blockula node. `status` is the HTTP status (429 = rate limited). */
|
|
176
|
+
export class ApiError extends Error {
|
|
177
|
+
status;
|
|
178
|
+
constructor(status, message) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.status = status;
|
|
181
|
+
this.name = "ApiError";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** A transaction that was rejected at execution (fee charged) or dropped from the mempool. */
|
|
185
|
+
export class TxFailedError extends Error {
|
|
186
|
+
id;
|
|
187
|
+
reason;
|
|
188
|
+
tx;
|
|
189
|
+
constructor(id, reason, tx) {
|
|
190
|
+
super(`tx ${id} failed: ${reason}`);
|
|
191
|
+
this.id = id;
|
|
192
|
+
this.reason = reason;
|
|
193
|
+
this.tx = tx;
|
|
194
|
+
this.name = "TxFailedError";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
119
197
|
export class Client {
|
|
120
198
|
rpc;
|
|
121
199
|
constructor(rpc = DEFAULT_RPC) {
|
|
@@ -123,9 +201,16 @@ export class Client {
|
|
|
123
201
|
}
|
|
124
202
|
async call(path, body) {
|
|
125
203
|
const res = await fetch(this.rpc.replace(/\/$/, "") + "/api" + path, body === undefined ? undefined : { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
126
|
-
const
|
|
204
|
+
const text = await res.text();
|
|
205
|
+
let json;
|
|
206
|
+
try {
|
|
207
|
+
json = JSON.parse(text);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
throw new ApiError(res.status, res.ok ? "the node returned something that isn't JSON" : `node error ${res.status} ${res.statusText}`.trim());
|
|
211
|
+
}
|
|
127
212
|
if (!res.ok)
|
|
128
|
-
throw new
|
|
213
|
+
throw new ApiError(res.status, json.error ?? res.statusText);
|
|
129
214
|
return json;
|
|
130
215
|
}
|
|
131
216
|
status() { return this.call("/status"); }
|
|
@@ -137,9 +222,18 @@ export class Client {
|
|
|
137
222
|
/** Accounts a key is authorized on (its own address, plus any it was added to). */
|
|
138
223
|
keyAccounts(keyIdHex) { return this.call(`/key/${keyIdHex}`); }
|
|
139
224
|
search(q) { return this.call(`/search/${encodeURIComponent(q)}`); }
|
|
225
|
+
/** Active set for the current epoch, pending/approved validators, next-epoch set, epoch timing. */
|
|
226
|
+
validators() { return this.call("/validators"); }
|
|
227
|
+
/** Hunts (bounties on GitHub issues), biggest first. */
|
|
228
|
+
hunts(status) { return this.call(`/hunts${status ? `?status=${status}` : ""}`); }
|
|
229
|
+
hunt(id) { return this.call(`/hunt/${id}`); }
|
|
230
|
+
/** Off-chain check of a GitHub link (gist owned by the handle containing the address). */
|
|
231
|
+
github(handle) { return this.call(`/github/${encodeURIComponent(handle)}`); }
|
|
140
232
|
async resolve(nameOrAddress) {
|
|
141
233
|
if (isAddress(nameOrAddress))
|
|
142
234
|
return nameOrAddress;
|
|
235
|
+
if (/^boo1/i.test(nameOrAddress.trim()))
|
|
236
|
+
throw new Error(`${nameOrAddress} isn't a valid address (check for typos; addresses are lowercase)`);
|
|
143
237
|
const r = await this.name(nameOrAddress);
|
|
144
238
|
if (r.available)
|
|
145
239
|
throw new Error(`${r.name} isn't registered`);
|
|
@@ -159,6 +253,8 @@ export class Client {
|
|
|
159
253
|
const tx = await this.tx(id);
|
|
160
254
|
if (tx.status === "finalized")
|
|
161
255
|
return tx;
|
|
256
|
+
if (tx.status === "failed")
|
|
257
|
+
throw new TxFailedError(id, tx.error ?? "unknown reason", tx);
|
|
162
258
|
if (Date.now() - start > timeoutMs)
|
|
163
259
|
throw new Error(`tx ${id} still pending after ${timeoutMs}ms`);
|
|
164
260
|
await new Promise((r) => setTimeout(r, 250));
|
package/dist/passkey.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ export declare function createPasskeyWallet(opts: {
|
|
|
4
4
|
username: string;
|
|
5
5
|
client?: Client;
|
|
6
6
|
rpName?: string;
|
|
7
|
+
rpId?: string;
|
|
7
8
|
}): Promise<Wallet>;
|
|
8
9
|
/** Unlock an existing passkey wallet (shows the OS passkey picker). */
|
|
9
10
|
export declare function unlockPasskeyWallet(opts?: {
|
|
10
11
|
client?: Client;
|
|
11
12
|
credentialId?: Uint8Array;
|
|
13
|
+
rpId?: string;
|
|
12
14
|
}): Promise<Wallet>;
|
package/dist/passkey.js
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
// used on-chain, so the account stays quantum-safe.
|
|
7
7
|
import { Wallet } from "./index.js";
|
|
8
8
|
const PRF_SALT = new TextEncoder().encode("blockula/wallet/v1");
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The WebAuthn relying-party id. Passkeys (and so wallets) are bound to it: a wallet created
|
|
11
|
+
* under one rpId can't be unlocked under another. Pin it to your production domain; it must be
|
|
12
|
+
* the page's hostname or a registrable parent of it.
|
|
13
|
+
*/
|
|
14
|
+
function rpId(explicit) {
|
|
15
|
+
return explicit ?? location.hostname;
|
|
11
16
|
}
|
|
12
17
|
function walletFrom(cred, prf, client) {
|
|
13
18
|
const wallet = Wallet.fromPasskeyPrf(prf, client);
|
|
@@ -27,7 +32,7 @@ async function prfFromCredential(cred) {
|
|
|
27
32
|
export async function createPasskeyWallet(opts) {
|
|
28
33
|
const cred = (await navigator.credentials.create({
|
|
29
34
|
publicKey: {
|
|
30
|
-
rp: { id: rpId(), name: opts.rpName ?? "Blockula" },
|
|
35
|
+
rp: { id: rpId(opts.rpId), name: opts.rpName ?? "Blockula" },
|
|
31
36
|
user: { id: crypto.getRandomValues(new Uint8Array(16)), name: opts.username, displayName: opts.username },
|
|
32
37
|
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
33
38
|
pubKeyCredParams: [{ type: "public-key", alg: -7 }, { type: "public-key", alg: -8 }, { type: "public-key", alg: -257 }],
|
|
@@ -41,14 +46,14 @@ export async function createPasskeyWallet(opts) {
|
|
|
41
46
|
if (ext?.prf?.results?.first)
|
|
42
47
|
return walletFrom(cred, new Uint8Array(ext.prf.results.first), opts.client);
|
|
43
48
|
if (ext?.prf?.enabled)
|
|
44
|
-
return unlockPasskeyWallet({ client: opts.client, credentialId: cred ? new Uint8Array(cred.rawId) : undefined });
|
|
49
|
+
return unlockPasskeyWallet({ client: opts.client, rpId: opts.rpId, credentialId: cred ? new Uint8Array(cred.rawId) : undefined });
|
|
45
50
|
return walletFrom(cred, await prfFromCredential(cred), opts.client);
|
|
46
51
|
}
|
|
47
52
|
/** Unlock an existing passkey wallet (shows the OS passkey picker). */
|
|
48
53
|
export async function unlockPasskeyWallet(opts = {}) {
|
|
49
54
|
const cred = (await navigator.credentials.get({
|
|
50
55
|
publicKey: {
|
|
51
|
-
rpId: rpId(),
|
|
56
|
+
rpId: rpId(opts.rpId),
|
|
52
57
|
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
53
58
|
userVerification: "required",
|
|
54
59
|
allowCredentials: opts.credentialId ? [{ type: "public-key", id: opts.credentialId }] : [],
|
package/dist/tx.d.ts
CHANGED
|
@@ -23,6 +23,66 @@ export type Action = {
|
|
|
23
23
|
} | {
|
|
24
24
|
type: "remove_key";
|
|
25
25
|
key_id: string;
|
|
26
|
+
}
|
|
27
|
+
/** Bond `bond` drops and offer `consensus_key` (hex ML-DSA-65 public key) as a validator. */
|
|
28
|
+
| {
|
|
29
|
+
type: "register_validator";
|
|
30
|
+
consensus_key: string;
|
|
31
|
+
p2p_addr: string;
|
|
32
|
+
http_addr: string;
|
|
33
|
+
bond: string;
|
|
34
|
+
/** Proof of possession: the consensus key's ML-DSA-65 signature over `popMessage(...)` with context `POP_CONTEXT` (see `blockula validator pop`). */
|
|
35
|
+
pop: string;
|
|
36
|
+
}
|
|
37
|
+
/** Season 0 allowlist (admin accounts only). */
|
|
38
|
+
| {
|
|
39
|
+
type: "approve_validator";
|
|
40
|
+
validator: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "remove_validator";
|
|
43
|
+
validator: string;
|
|
44
|
+
}
|
|
45
|
+
/** Reclaim the bond of a removed validator you own, one epoch after it left. */
|
|
46
|
+
| {
|
|
47
|
+
type: "unbond";
|
|
48
|
+
validator: string;
|
|
49
|
+
}
|
|
50
|
+
/** Claim a GitHub handle; a public gist by that handle containing your boo1 address verifies it. */
|
|
51
|
+
| {
|
|
52
|
+
type: "link_github";
|
|
53
|
+
handle: string;
|
|
54
|
+
gist_id: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: "unlink_github";
|
|
57
|
+
handle: string;
|
|
58
|
+
}
|
|
59
|
+
/** Escrow a bounty on a GitHub issue ("owner/name", issue number). */
|
|
60
|
+
| {
|
|
61
|
+
type: "create_hunt";
|
|
62
|
+
repo: string;
|
|
63
|
+
issue: number;
|
|
64
|
+
amount: string;
|
|
65
|
+
expires_at: number;
|
|
66
|
+
title: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: "fund_hunt";
|
|
69
|
+
hunt_id: number;
|
|
70
|
+
amount: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: "award_hunt";
|
|
73
|
+
hunt_id: number;
|
|
74
|
+
to: string;
|
|
75
|
+
pr: number;
|
|
76
|
+
familiar_key?: string;
|
|
77
|
+
} | {
|
|
78
|
+
type: "cancel_hunt";
|
|
79
|
+
hunt_id: number;
|
|
80
|
+
}
|
|
81
|
+
/** Mark one of your session keys as an agent Familiar. */
|
|
82
|
+
| {
|
|
83
|
+
type: "register_familiar";
|
|
84
|
+
key_id: string;
|
|
85
|
+
label: string;
|
|
26
86
|
};
|
|
27
87
|
export interface UnsignedTx {
|
|
28
88
|
chain_id: string;
|
|
@@ -41,6 +101,10 @@ export declare const PUBLIC_KEY_LEN = 1952;
|
|
|
41
101
|
export declare const SIGNATURE_LEN = 3309;
|
|
42
102
|
export declare const DROPS_PER_FANG = 1000000000n;
|
|
43
103
|
export declare function signingBytes(tx: UnsignedTx): Uint8Array;
|
|
104
|
+
/** FIPS 204 context string for validator proofs of possession (node/src/types.rs `POP_CONTEXT`). */
|
|
105
|
+
export declare const POP_CONTEXT: Uint8Array<ArrayBuffer>;
|
|
106
|
+
/** What a validator's consensus key signs to register (node/src/types.rs `pop_message`). */
|
|
107
|
+
export declare function popMessage(chainId: string, owner: string, p2pAddr: string, httpAddr: string): Uint8Array;
|
|
44
108
|
/** Mirrors `min_fee` in node/src/types.rs: base fee is priced per byte and burned. */
|
|
45
109
|
export declare function minFee(tx: UnsignedTx): bigint;
|
|
46
110
|
/** "1.5" → 1500000000n drops. Accepts numbers, decimal strings or bigint drops. */
|
package/dist/tx.js
CHANGED
|
@@ -49,10 +49,78 @@ export function signingBytes(tx) {
|
|
|
49
49
|
w.u8(3);
|
|
50
50
|
w.str(a.key_id);
|
|
51
51
|
break;
|
|
52
|
+
case "register_validator":
|
|
53
|
+
w.u8(4);
|
|
54
|
+
w.str(a.consensus_key);
|
|
55
|
+
w.str(a.p2p_addr);
|
|
56
|
+
w.str(a.http_addr);
|
|
57
|
+
w.u64(BigInt(a.bond));
|
|
58
|
+
w.str(a.pop);
|
|
59
|
+
break;
|
|
60
|
+
case "approve_validator":
|
|
61
|
+
w.u8(5);
|
|
62
|
+
w.str(a.validator);
|
|
63
|
+
break;
|
|
64
|
+
case "remove_validator":
|
|
65
|
+
w.u8(6);
|
|
66
|
+
w.str(a.validator);
|
|
67
|
+
break;
|
|
68
|
+
case "unbond":
|
|
69
|
+
w.u8(7);
|
|
70
|
+
w.str(a.validator);
|
|
71
|
+
break;
|
|
72
|
+
case "link_github":
|
|
73
|
+
w.u8(8);
|
|
74
|
+
w.str(a.handle);
|
|
75
|
+
w.str(a.gist_id);
|
|
76
|
+
break;
|
|
77
|
+
case "unlink_github":
|
|
78
|
+
w.u8(9);
|
|
79
|
+
w.str(a.handle);
|
|
80
|
+
break;
|
|
81
|
+
case "create_hunt":
|
|
82
|
+
w.u8(10);
|
|
83
|
+
w.str(a.repo);
|
|
84
|
+
w.u64(BigInt(a.issue));
|
|
85
|
+
w.u64(BigInt(a.amount));
|
|
86
|
+
w.u64(BigInt(a.expires_at));
|
|
87
|
+
w.str(a.title);
|
|
88
|
+
break;
|
|
89
|
+
case "fund_hunt":
|
|
90
|
+
w.u8(11);
|
|
91
|
+
w.u64(BigInt(a.hunt_id));
|
|
92
|
+
w.u64(BigInt(a.amount));
|
|
93
|
+
break;
|
|
94
|
+
case "award_hunt":
|
|
95
|
+
w.u8(12);
|
|
96
|
+
w.u64(BigInt(a.hunt_id));
|
|
97
|
+
w.str(a.to);
|
|
98
|
+
w.u64(BigInt(a.pr));
|
|
99
|
+
w.str(a.familiar_key ?? "");
|
|
100
|
+
break;
|
|
101
|
+
case "cancel_hunt":
|
|
102
|
+
w.u8(13);
|
|
103
|
+
w.u64(BigInt(a.hunt_id));
|
|
104
|
+
break;
|
|
105
|
+
case "register_familiar":
|
|
106
|
+
w.u8(14);
|
|
107
|
+
w.str(a.key_id);
|
|
108
|
+
w.str(a.label);
|
|
109
|
+
break;
|
|
52
110
|
}
|
|
53
111
|
w.str(tx.memo);
|
|
54
112
|
return w.done();
|
|
55
113
|
}
|
|
114
|
+
/** FIPS 204 context string for validator proofs of possession (node/src/types.rs `POP_CONTEXT`). */
|
|
115
|
+
export const POP_CONTEXT = utf8.encode("blockula/validator-pop/v1");
|
|
116
|
+
/** What a validator's consensus key signs to register (node/src/types.rs `pop_message`). */
|
|
117
|
+
export function popMessage(chainId, owner, p2pAddr, httpAddr) {
|
|
118
|
+
const w = new Writer();
|
|
119
|
+
w.bytes(utf8.encode("BLOCKULA_VALIDATOR_POP_V1"));
|
|
120
|
+
for (const s of [chainId, owner, p2pAddr, httpAddr])
|
|
121
|
+
w.str(s);
|
|
122
|
+
return w.done();
|
|
123
|
+
}
|
|
56
124
|
/** Mirrors `min_fee` in node/src/types.rs: base fee is priced per byte and burned. */
|
|
57
125
|
export function minFee(tx) {
|
|
58
126
|
const size = signingBytes(tx).length + PUBLIC_KEY_LEN + SIGNATURE_LEN;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockula/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "\ud83e\udddb TypeScript SDK for Blockula \u2014 post-quantum wallets (ML-DSA-65), .boo names, transfers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,5 +44,14 @@
|
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/jackdishman/blockula.git",
|
|
51
|
+
"directory": "sdk-ts"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/jackdishman/blockula#readme",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/jackdishman/blockula/issues"
|
|
47
56
|
}
|
|
48
57
|
}
|