@bondedhq/solana 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +25 -2
- package/dist/idls.d.ts +5 -0
- package/dist/idls.js +2414 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +389 -0
- package/package.json +55 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { type Commitment, type Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
import { bondedCoverIdl, bondedRegistryIdl, bondedVaultIdl } from "./idls.js";
|
|
3
|
+
/**
|
|
4
|
+
* TypeScript client for the Bonded Solana programs (docs/decisions/011-solana-programs.md).
|
|
5
|
+
* Accounts are decoded and instructions encoded from the programs' own IDLs (src/idls.ts,
|
|
6
|
+
* generated from the Anchor build), so layouts can't drift from the Rust. The Rust twin is
|
|
7
|
+
* contracts/solana/crates/bonded_client; both derive the same addresses.
|
|
8
|
+
*/
|
|
9
|
+
export { bondedCoverIdl, bondedRegistryIdl, bondedVaultIdl };
|
|
10
|
+
export declare const PROGRAM_IDS: {
|
|
11
|
+
readonly registry: PublicKey;
|
|
12
|
+
readonly vault: PublicKey;
|
|
13
|
+
readonly cover: PublicKey;
|
|
14
|
+
};
|
|
15
|
+
export declare const TOKEN_PROGRAM: PublicKey;
|
|
16
|
+
/** Program-derived addresses, with the same seeds as the programs and bonded_client. */
|
|
17
|
+
export declare const address: {
|
|
18
|
+
registryConfig: () => PublicKey;
|
|
19
|
+
agent: (agentId: bigint) => PublicKey;
|
|
20
|
+
vaultConfig: () => PublicKey;
|
|
21
|
+
vault: (guardian: PublicKey, agentId: bigint) => PublicKey;
|
|
22
|
+
vaultBase: (vault: PublicKey) => PublicKey;
|
|
23
|
+
coverConfig: () => PublicKey;
|
|
24
|
+
poolVault: () => PublicKey;
|
|
25
|
+
claimsAuthority: () => PublicKey;
|
|
26
|
+
bond: (agentId: bigint) => PublicKey;
|
|
27
|
+
bondVault: (agentId: bigint) => PublicKey;
|
|
28
|
+
policy: (policyId: bigint) => PublicKey;
|
|
29
|
+
};
|
|
30
|
+
export interface Breach {
|
|
31
|
+
/** The breach's index in the vault's log: what a claim names. */
|
|
32
|
+
id: number;
|
|
33
|
+
type: string;
|
|
34
|
+
blocked: boolean;
|
|
35
|
+
claimed: boolean;
|
|
36
|
+
target: PublicKey;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
/** Base-asset units that left the vault. */
|
|
39
|
+
loss: bigint;
|
|
40
|
+
}
|
|
41
|
+
export interface Vault {
|
|
42
|
+
address: PublicKey;
|
|
43
|
+
guardian: PublicKey;
|
|
44
|
+
agent: PublicKey;
|
|
45
|
+
agentId: bigint;
|
|
46
|
+
baseMint: PublicKey;
|
|
47
|
+
frozen: boolean;
|
|
48
|
+
breaches: Breach[];
|
|
49
|
+
}
|
|
50
|
+
/** Everything a vault account holds: what `execute` checks an action against. */
|
|
51
|
+
export interface VaultAccount extends Vault {
|
|
52
|
+
baseAccount: PublicKey;
|
|
53
|
+
mandateHash: Uint8Array;
|
|
54
|
+
/** Enforce mode: a breaching action is stopped (Monitor lets it through and records it). */
|
|
55
|
+
enforce: boolean;
|
|
56
|
+
freezeOnBreach: boolean;
|
|
57
|
+
/** Programs the agent may call through the vault. */
|
|
58
|
+
allowedPrograms: PublicKey[];
|
|
59
|
+
/** Largest net base-asset outflow of one call, in base units (the T2 limit). */
|
|
60
|
+
maxOutflowPerCall: bigint;
|
|
61
|
+
}
|
|
62
|
+
export interface Policy {
|
|
63
|
+
address: PublicKey;
|
|
64
|
+
policyId: bigint;
|
|
65
|
+
agentId: bigint;
|
|
66
|
+
vault: PublicKey;
|
|
67
|
+
coverAmount: bigint;
|
|
68
|
+
remainingCover: bigint;
|
|
69
|
+
startedAt: number;
|
|
70
|
+
endsAt: number;
|
|
71
|
+
active: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface CoverConfig {
|
|
74
|
+
baseMint: PublicKey;
|
|
75
|
+
claimWindow: number;
|
|
76
|
+
nextPolicyId: bigint;
|
|
77
|
+
}
|
|
78
|
+
export declare function decodeVault(address: PublicKey, data: Buffer): VaultAccount;
|
|
79
|
+
export declare function decodePolicy(address: PublicKey, data: Buffer): Policy;
|
|
80
|
+
export declare function decodeCoverConfig(data: Buffer): CoverConfig;
|
|
81
|
+
export declare function fetchVaults(connection: Connection): Promise<VaultAccount[]>;
|
|
82
|
+
export declare function fetchPolicies(connection: Connection): Promise<Policy[]>;
|
|
83
|
+
export declare function fetchCoverConfig(connection: Connection): Promise<CoverConfig>;
|
|
84
|
+
/** One vault, or null if there is no account at `vault`. */
|
|
85
|
+
export declare function fetchVault(connection: Connection, vault: PublicKey, commitment?: Commitment): Promise<VaultAccount | null>;
|
|
86
|
+
export interface VaultParams {
|
|
87
|
+
agentId: bigint;
|
|
88
|
+
/** The agent's session key: the only key that can call `execute`. */
|
|
89
|
+
agent: PublicKey;
|
|
90
|
+
mandateHash: Uint8Array;
|
|
91
|
+
enforce: boolean;
|
|
92
|
+
freezeOnBreach: boolean;
|
|
93
|
+
allowedPrograms: PublicKey[];
|
|
94
|
+
/** Largest net base-asset outflow of one call, in base units. */
|
|
95
|
+
maxOutflowPerCall: bigint;
|
|
96
|
+
}
|
|
97
|
+
/** Create a vault for one agent; `guardian` signs and pays. The vault's address is `address.vault`. */
|
|
98
|
+
export declare function createVaultInstruction(guardian: PublicKey, baseMint: PublicKey, params: VaultParams): TransactionInstruction;
|
|
99
|
+
/** Add `amount` base units to the vault from `from`, a token account `depositor` controls. */
|
|
100
|
+
export declare function depositInstruction(vault: Pick<Vault, "address" | "baseMint">, from: PublicKey, depositor: PublicKey, amount: bigint): TransactionInstruction;
|
|
101
|
+
/**
|
|
102
|
+
* Wrap `inner`, an instruction the vault should perform (the vault as its signer, e.g. as a
|
|
103
|
+
* token account's owner), in the vault's `execute`, signed by the agent's key. The program
|
|
104
|
+
* checks it against the mandate before running it.
|
|
105
|
+
*/
|
|
106
|
+
export declare function executeInstruction(vault: Pick<Vault, "address" | "agent">, inner: TransactionInstruction): TransactionInstruction;
|
|
107
|
+
/** A breach the vault recorded, from its `BreachRecorded` event. */
|
|
108
|
+
export interface BreachEvent {
|
|
109
|
+
breachId: number;
|
|
110
|
+
type: string;
|
|
111
|
+
target: PublicKey;
|
|
112
|
+
/** Base-asset units that left the vault (0 when blocked). */
|
|
113
|
+
loss: bigint;
|
|
114
|
+
/** Enforce mode stopped the call: nothing moved. */
|
|
115
|
+
blocked: boolean;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The `BreachRecorded` events in a transaction's logs. Only events the vault program itself
|
|
119
|
+
* emitted count: a program the vault calls can print anything, so lines logged while another
|
|
120
|
+
* program is running are ignored.
|
|
121
|
+
*/
|
|
122
|
+
export declare function breachEventsFromLogs(logs: readonly string[]): BreachEvent[];
|
|
123
|
+
/** The vault program's error for a custom error code (e.g. 6000 is `Frozen`), if it is one. */
|
|
124
|
+
export declare function vaultError(code: number): {
|
|
125
|
+
name: string;
|
|
126
|
+
message: string;
|
|
127
|
+
} | undefined;
|
|
128
|
+
export interface ClaimPlan {
|
|
129
|
+
vault: Vault;
|
|
130
|
+
policy: Policy;
|
|
131
|
+
breachIds: number[];
|
|
132
|
+
loss: bigint;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The claims the cover program would accept now, applying its own rules: the policy is active
|
|
136
|
+
* and its claim window open, and each breach is unclaimed, not blocked, lost something and
|
|
137
|
+
* happened within the policy's term. One plan per vault and policy.
|
|
138
|
+
*/
|
|
139
|
+
export declare function claimable(vaults: readonly Vault[], policies: readonly Policy[], now: number, claimWindow: number): ClaimPlan[];
|
|
140
|
+
/** File a claim for `plan`. Anyone can send it; the payout goes to the vault. */
|
|
141
|
+
export declare function fileClaimInstruction(plan: ClaimPlan): TransactionInstruction;
|
|
142
|
+
/** A score as the Solana registry stores it: the EVM `BondedTypes.Attestation` fields. */
|
|
143
|
+
export interface SolanaAttestation {
|
|
144
|
+
agentId: bigint;
|
|
145
|
+
score: number;
|
|
146
|
+
expectedLossBps: number;
|
|
147
|
+
reportHash: Uint8Array;
|
|
148
|
+
mandateHash: Uint8Array;
|
|
149
|
+
codeHash: Uint8Array;
|
|
150
|
+
teeMeasurement: Uint8Array;
|
|
151
|
+
modelFamily: number;
|
|
152
|
+
issuedAt: number;
|
|
153
|
+
expiresAt: number;
|
|
154
|
+
}
|
|
155
|
+
export interface RegistryAgent {
|
|
156
|
+
agentId: bigint;
|
|
157
|
+
owner: PublicKey;
|
|
158
|
+
uri: string;
|
|
159
|
+
score: SolanaAttestation | null;
|
|
160
|
+
}
|
|
161
|
+
/** Domain tag the registry prefixes to every signed attestation (bonded_registry). */
|
|
162
|
+
export declare const ATTESTATION_DOMAIN: Buffer<ArrayBuffer>;
|
|
163
|
+
/**
|
|
164
|
+
* The exact bytes the attester signs: the domain tag, the registry's program id, then the
|
|
165
|
+
* Borsh-encoded attestation (bonded_registry::attestation_message).
|
|
166
|
+
*/
|
|
167
|
+
export declare function attestationMessage(attestation: SolanaAttestation): Buffer;
|
|
168
|
+
export declare function fetchRegistryConfig(connection: Connection): Promise<{
|
|
169
|
+
attester: PublicKey;
|
|
170
|
+
agentCount: bigint;
|
|
171
|
+
}>;
|
|
172
|
+
export declare function fetchAgent(connection: Connection, agentId: bigint): Promise<RegistryAgent | null>;
|
|
173
|
+
/** Register the next agent (id = agent count + 1), owned by `owner`, who signs and pays. */
|
|
174
|
+
export declare function registerInstruction(owner: PublicKey, nextAgentId: bigint, uri: string): TransactionInstruction;
|
|
175
|
+
/**
|
|
176
|
+
* Post a score: the Ed25519 check of the attester's signature over `attestationMessage`, then
|
|
177
|
+
* the registry instruction, which looks for that check just before it.
|
|
178
|
+
*/
|
|
179
|
+
export declare function postScoreInstructions(attestation: SolanaAttestation, attester: {
|
|
180
|
+
publicKey: PublicKey;
|
|
181
|
+
secretKey: Uint8Array;
|
|
182
|
+
}): TransactionInstruction[];
|
|
183
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { BorshCoder } from "@anchor-lang/core";
|
|
2
|
+
import { Ed25519Program, PublicKey, SystemProgram, SYSVAR_INSTRUCTIONS_PUBKEY, TransactionInstruction, } from "@solana/web3.js";
|
|
3
|
+
import BN from "bn.js";
|
|
4
|
+
import { bondedCoverIdl, bondedRegistryIdl, bondedVaultIdl } from "./idls.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript client for the Bonded Solana programs (docs/decisions/011-solana-programs.md).
|
|
7
|
+
* Accounts are decoded and instructions encoded from the programs' own IDLs (src/idls.ts,
|
|
8
|
+
* generated from the Anchor build), so layouts can't drift from the Rust. The Rust twin is
|
|
9
|
+
* contracts/solana/crates/bonded_client; both derive the same addresses.
|
|
10
|
+
*/
|
|
11
|
+
export { bondedCoverIdl, bondedRegistryIdl, bondedVaultIdl };
|
|
12
|
+
export const PROGRAM_IDS = {
|
|
13
|
+
registry: new PublicKey(bondedRegistryIdl.address),
|
|
14
|
+
vault: new PublicKey(bondedVaultIdl.address),
|
|
15
|
+
cover: new PublicKey(bondedCoverIdl.address),
|
|
16
|
+
};
|
|
17
|
+
export const TOKEN_PROGRAM = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
18
|
+
const SYSTEM_PROGRAM = SystemProgram.programId;
|
|
19
|
+
const coders = {
|
|
20
|
+
vault: new BorshCoder(bondedVaultIdl),
|
|
21
|
+
cover: new BorshCoder(bondedCoverIdl),
|
|
22
|
+
registry: new BorshCoder(bondedRegistryIdl),
|
|
23
|
+
};
|
|
24
|
+
const u64 = (n) => new BN(n.toString()).toArrayLike(Buffer, "le", 8);
|
|
25
|
+
const pda = (seeds, program) => PublicKey.findProgramAddressSync(seeds, program)[0];
|
|
26
|
+
/** Program-derived addresses, with the same seeds as the programs and bonded_client. */
|
|
27
|
+
export const address = {
|
|
28
|
+
registryConfig: () => pda([Buffer.from("config")], PROGRAM_IDS.registry),
|
|
29
|
+
agent: (agentId) => pda([Buffer.from("agent"), u64(agentId)], PROGRAM_IDS.registry),
|
|
30
|
+
vaultConfig: () => pda([Buffer.from("config")], PROGRAM_IDS.vault),
|
|
31
|
+
vault: (guardian, agentId) => pda([Buffer.from("vault"), guardian.toBuffer(), u64(agentId)], PROGRAM_IDS.vault),
|
|
32
|
+
vaultBase: (vault) => pda([Buffer.from("base"), vault.toBuffer()], PROGRAM_IDS.vault),
|
|
33
|
+
coverConfig: () => pda([Buffer.from("config")], PROGRAM_IDS.cover),
|
|
34
|
+
poolVault: () => pda([Buffer.from("pool_vault")], PROGRAM_IDS.cover),
|
|
35
|
+
claimsAuthority: () => pda([Buffer.from("claims")], PROGRAM_IDS.cover),
|
|
36
|
+
bond: (agentId) => pda([Buffer.from("bond"), u64(agentId)], PROGRAM_IDS.cover),
|
|
37
|
+
bondVault: (agentId) => pda([Buffer.from("bond_vault"), u64(agentId)], PROGRAM_IDS.cover),
|
|
38
|
+
policy: (policyId) => pda([Buffer.from("policy"), u64(policyId)], PROGRAM_IDS.cover),
|
|
39
|
+
};
|
|
40
|
+
const big = (v) => BigInt(v.toString());
|
|
41
|
+
const num = (v) => Number(v.toString());
|
|
42
|
+
export function decodeVault(address, data) {
|
|
43
|
+
const v = coders.vault.accounts.decode("Vault", data);
|
|
44
|
+
return {
|
|
45
|
+
address,
|
|
46
|
+
guardian: v.guardian,
|
|
47
|
+
agent: v.agent,
|
|
48
|
+
agentId: big(v.agent_id),
|
|
49
|
+
baseMint: v.base_mint,
|
|
50
|
+
baseAccount: v.base_account,
|
|
51
|
+
mandateHash: Uint8Array.from(v.mandate_hash),
|
|
52
|
+
enforce: v.enforce,
|
|
53
|
+
freezeOnBreach: v.freeze_on_breach,
|
|
54
|
+
allowedPrograms: v.allowed_programs,
|
|
55
|
+
maxOutflowPerCall: big(v.max_outflow_per_call),
|
|
56
|
+
frozen: v.frozen,
|
|
57
|
+
breaches: v.breaches.map((b, id) => ({
|
|
58
|
+
id,
|
|
59
|
+
type: Object.keys(b.breach_type)[0],
|
|
60
|
+
blocked: b.blocked,
|
|
61
|
+
claimed: b.claimed,
|
|
62
|
+
target: b.target,
|
|
63
|
+
timestamp: num(b.timestamp),
|
|
64
|
+
loss: big(b.loss),
|
|
65
|
+
})),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function decodePolicy(address, data) {
|
|
69
|
+
const p = coders.cover.accounts.decode("Policy", data);
|
|
70
|
+
return {
|
|
71
|
+
address,
|
|
72
|
+
policyId: big(p.policy_id),
|
|
73
|
+
agentId: big(p.agent_id),
|
|
74
|
+
vault: p.vault,
|
|
75
|
+
coverAmount: big(p.cover_amount),
|
|
76
|
+
remainingCover: big(p.remaining_cover),
|
|
77
|
+
startedAt: num(p.started_at),
|
|
78
|
+
endsAt: num(p.ends_at),
|
|
79
|
+
active: p.active,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function decodeCoverConfig(data) {
|
|
83
|
+
const c = coders.cover.accounts.decode("Config", data);
|
|
84
|
+
return {
|
|
85
|
+
baseMint: c.base_mint,
|
|
86
|
+
claimWindow: num(c.claim_window),
|
|
87
|
+
nextPolicyId: big(c.next_policy_id),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------- reading the chain
|
|
91
|
+
/** Every account of one type a program owns, found by its Anchor discriminator. */
|
|
92
|
+
async function accountsOf(connection, idl, program, name) {
|
|
93
|
+
const discriminator = idl.accounts.find((a) => a.name === name).discriminator;
|
|
94
|
+
return connection.getProgramAccounts(program, {
|
|
95
|
+
filters: [{ memcmp: { offset: 0, bytes: bs58(Buffer.from(discriminator)) } }],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function bs58(bytes) {
|
|
99
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
100
|
+
let n = BigInt(`0x${bytes.toString("hex") || "0"}`);
|
|
101
|
+
let out = "";
|
|
102
|
+
while (n > 0n) {
|
|
103
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
104
|
+
n /= 58n;
|
|
105
|
+
}
|
|
106
|
+
for (const b of bytes) {
|
|
107
|
+
if (b !== 0)
|
|
108
|
+
break;
|
|
109
|
+
out = `1${out}`;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
export async function fetchVaults(connection) {
|
|
114
|
+
const accounts = await accountsOf(connection, bondedVaultIdl, PROGRAM_IDS.vault, "Vault");
|
|
115
|
+
return accounts.map((a) => decodeVault(a.pubkey, a.account.data));
|
|
116
|
+
}
|
|
117
|
+
export async function fetchPolicies(connection) {
|
|
118
|
+
const accounts = await accountsOf(connection, bondedCoverIdl, PROGRAM_IDS.cover, "Policy");
|
|
119
|
+
return accounts.map((a) => decodePolicy(a.pubkey, a.account.data));
|
|
120
|
+
}
|
|
121
|
+
export async function fetchCoverConfig(connection) {
|
|
122
|
+
const info = await connection.getAccountInfo(address.coverConfig());
|
|
123
|
+
if (!info)
|
|
124
|
+
throw new Error("the cover program isn't initialized on this cluster");
|
|
125
|
+
return decodeCoverConfig(info.data);
|
|
126
|
+
}
|
|
127
|
+
/** One vault, or null if there is no account at `vault`. */
|
|
128
|
+
export async function fetchVault(connection, vault, commitment) {
|
|
129
|
+
const info = await connection.getAccountInfo(vault, commitment);
|
|
130
|
+
return info ? decodeVault(vault, info.data) : null;
|
|
131
|
+
}
|
|
132
|
+
/** Create a vault for one agent; `guardian` signs and pays. The vault's address is `address.vault`. */
|
|
133
|
+
export function createVaultInstruction(guardian, baseMint, params) {
|
|
134
|
+
const vault = address.vault(guardian, params.agentId);
|
|
135
|
+
return new TransactionInstruction({
|
|
136
|
+
programId: PROGRAM_IDS.vault,
|
|
137
|
+
data: coders.vault.instruction.encode("create_vault", {
|
|
138
|
+
params: {
|
|
139
|
+
agent_id: new BN(params.agentId.toString()),
|
|
140
|
+
agent: params.agent,
|
|
141
|
+
mandate_hash: [...params.mandateHash],
|
|
142
|
+
enforce: params.enforce,
|
|
143
|
+
freeze_on_breach: params.freezeOnBreach,
|
|
144
|
+
allowed_programs: params.allowedPrograms,
|
|
145
|
+
max_outflow_per_call: new BN(params.maxOutflowPerCall.toString()),
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
keys: metas(bondedVaultIdl, "create_vault", {
|
|
149
|
+
vault,
|
|
150
|
+
base_account: address.vaultBase(vault),
|
|
151
|
+
base_mint: baseMint,
|
|
152
|
+
guardian,
|
|
153
|
+
token_program: TOKEN_PROGRAM,
|
|
154
|
+
system_program: SYSTEM_PROGRAM,
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Add `amount` base units to the vault from `from`, a token account `depositor` controls. */
|
|
159
|
+
export function depositInstruction(vault, from, depositor, amount) {
|
|
160
|
+
return new TransactionInstruction({
|
|
161
|
+
programId: PROGRAM_IDS.vault,
|
|
162
|
+
data: coders.vault.instruction.encode("deposit", { amount: new BN(amount.toString()) }),
|
|
163
|
+
keys: metas(bondedVaultIdl, "deposit", {
|
|
164
|
+
vault: vault.address,
|
|
165
|
+
base_account: address.vaultBase(vault.address),
|
|
166
|
+
base_mint: vault.baseMint,
|
|
167
|
+
from,
|
|
168
|
+
depositor,
|
|
169
|
+
token_program: TOKEN_PROGRAM,
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Wrap `inner`, an instruction the vault should perform (the vault as its signer, e.g. as a
|
|
175
|
+
* token account's owner), in the vault's `execute`, signed by the agent's key. The program
|
|
176
|
+
* checks it against the mandate before running it.
|
|
177
|
+
*/
|
|
178
|
+
export function executeInstruction(vault, inner) {
|
|
179
|
+
// The vault signs the inner call by PDA seeds, never as a transaction signer.
|
|
180
|
+
const remaining = inner.keys.map((key) => key.pubkey.equals(vault.address) ? { ...key, isSigner: false } : key);
|
|
181
|
+
return new TransactionInstruction({
|
|
182
|
+
programId: PROGRAM_IDS.vault,
|
|
183
|
+
data: coders.vault.instruction.encode("execute", { data: inner.data }),
|
|
184
|
+
keys: [
|
|
185
|
+
...metas(bondedVaultIdl, "execute", {
|
|
186
|
+
vault: vault.address,
|
|
187
|
+
agent: vault.agent,
|
|
188
|
+
base_account: address.vaultBase(vault.address),
|
|
189
|
+
target_program: inner.programId,
|
|
190
|
+
}),
|
|
191
|
+
...remaining,
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The `BreachRecorded` events in a transaction's logs. Only events the vault program itself
|
|
197
|
+
* emitted count: a program the vault calls can print anything, so lines logged while another
|
|
198
|
+
* program is running are ignored.
|
|
199
|
+
*/
|
|
200
|
+
export function breachEventsFromLogs(logs) {
|
|
201
|
+
const vault = PROGRAM_IDS.vault.toBase58();
|
|
202
|
+
const stack = [];
|
|
203
|
+
const events = [];
|
|
204
|
+
for (const line of logs) {
|
|
205
|
+
const invoke = /^Program (\w+) invoke \[\d+\]$/.exec(line);
|
|
206
|
+
if (invoke) {
|
|
207
|
+
stack.push(invoke[1]);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (/^Program \w+ (success|failed)/.test(line)) {
|
|
211
|
+
stack.pop();
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (stack.at(-1) !== vault || !line.startsWith("Program data: "))
|
|
215
|
+
continue;
|
|
216
|
+
const event = coders.vault.events.decode(line.slice("Program data: ".length));
|
|
217
|
+
if (event?.name !== "BreachRecorded")
|
|
218
|
+
continue;
|
|
219
|
+
const e = event.data;
|
|
220
|
+
events.push({
|
|
221
|
+
breachId: e.breach_id,
|
|
222
|
+
type: Object.keys(e.breach_type)[0],
|
|
223
|
+
target: e.target,
|
|
224
|
+
loss: big(e.loss),
|
|
225
|
+
blocked: e.blocked,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return events;
|
|
229
|
+
}
|
|
230
|
+
/** The vault program's error for a custom error code (e.g. 6000 is `Frozen`), if it is one. */
|
|
231
|
+
export function vaultError(code) {
|
|
232
|
+
const error = bondedVaultIdl.errors?.find((e) => e.code === code);
|
|
233
|
+
return error ? { name: error.name, message: error.msg ?? error.name } : undefined;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The claims the cover program would accept now, applying its own rules: the policy is active
|
|
237
|
+
* and its claim window open, and each breach is unclaimed, not blocked, lost something and
|
|
238
|
+
* happened within the policy's term. One plan per vault and policy.
|
|
239
|
+
*/
|
|
240
|
+
export function claimable(vaults, policies, now, claimWindow) {
|
|
241
|
+
const plans = [];
|
|
242
|
+
for (const policy of policies) {
|
|
243
|
+
if (!policy.active || now > policy.endsAt + claimWindow)
|
|
244
|
+
continue;
|
|
245
|
+
const vault = vaults.find((v) => v.address.equals(policy.vault));
|
|
246
|
+
if (!vault)
|
|
247
|
+
continue;
|
|
248
|
+
const breaches = vault.breaches.filter((b) => !b.claimed &&
|
|
249
|
+
!b.blocked &&
|
|
250
|
+
b.loss > 0n &&
|
|
251
|
+
b.timestamp >= policy.startedAt &&
|
|
252
|
+
b.timestamp <= policy.endsAt);
|
|
253
|
+
if (breaches.length === 0)
|
|
254
|
+
continue;
|
|
255
|
+
plans.push({
|
|
256
|
+
vault,
|
|
257
|
+
policy,
|
|
258
|
+
breachIds: breaches.map((b) => b.id),
|
|
259
|
+
loss: breaches.reduce((s, b) => s + b.loss, 0n),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return plans;
|
|
263
|
+
}
|
|
264
|
+
/** The accounts an instruction takes, in the IDL's order, with its writable and signer flags. */
|
|
265
|
+
function metas(idl, instruction, accounts) {
|
|
266
|
+
const ix = idl.instructions.find((i) => i.name === instruction);
|
|
267
|
+
return ix.accounts.map((a) => {
|
|
268
|
+
const account = a;
|
|
269
|
+
const pubkey = accounts[account.name];
|
|
270
|
+
if (!pubkey)
|
|
271
|
+
throw new Error(`${instruction}: no address for account ${account.name}`);
|
|
272
|
+
return { pubkey, isWritable: !!account.writable, isSigner: !!account.signer };
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/** File a claim for `plan`. Anyone can send it; the payout goes to the vault. */
|
|
276
|
+
export function fileClaimInstruction(plan) {
|
|
277
|
+
const { vault, policy } = plan;
|
|
278
|
+
const data = coders.cover.instruction.encode("file_claim", {
|
|
279
|
+
breach_ids: Buffer.from(plan.breachIds),
|
|
280
|
+
});
|
|
281
|
+
return new TransactionInstruction({
|
|
282
|
+
programId: PROGRAM_IDS.cover,
|
|
283
|
+
data,
|
|
284
|
+
keys: metas(bondedCoverIdl, "file_claim", {
|
|
285
|
+
config: address.coverConfig(),
|
|
286
|
+
policy: policy.address,
|
|
287
|
+
vault: vault.address,
|
|
288
|
+
vault_base: address.vaultBase(vault.address),
|
|
289
|
+
bond: address.bond(policy.agentId),
|
|
290
|
+
bond_vault: address.bondVault(policy.agentId),
|
|
291
|
+
pool_vault: address.poolVault(),
|
|
292
|
+
base_mint: vault.baseMint,
|
|
293
|
+
claims_authority: address.claimsAuthority(),
|
|
294
|
+
vault_config: address.vaultConfig(),
|
|
295
|
+
vault_program: PROGRAM_IDS.vault,
|
|
296
|
+
token_program: TOKEN_PROGRAM,
|
|
297
|
+
}),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/** Domain tag the registry prefixes to every signed attestation (bonded_registry). */
|
|
301
|
+
export const ATTESTATION_DOMAIN = Buffer.from("bonded-attestation-v1");
|
|
302
|
+
const toRaw = (a) => ({
|
|
303
|
+
agent_id: new BN(a.agentId.toString()),
|
|
304
|
+
score: a.score,
|
|
305
|
+
expected_loss_bps: a.expectedLossBps,
|
|
306
|
+
report_hash: [...a.reportHash],
|
|
307
|
+
mandate_hash: [...a.mandateHash],
|
|
308
|
+
code_hash: [...a.codeHash],
|
|
309
|
+
tee_measurement: [...a.teeMeasurement],
|
|
310
|
+
model_family: a.modelFamily,
|
|
311
|
+
issued_at: new BN(a.issuedAt),
|
|
312
|
+
expires_at: new BN(a.expiresAt),
|
|
313
|
+
});
|
|
314
|
+
const fromRaw = (r) => ({
|
|
315
|
+
agentId: big(r.agent_id),
|
|
316
|
+
score: r.score,
|
|
317
|
+
expectedLossBps: r.expected_loss_bps,
|
|
318
|
+
reportHash: Uint8Array.from(r.report_hash),
|
|
319
|
+
mandateHash: Uint8Array.from(r.mandate_hash),
|
|
320
|
+
codeHash: Uint8Array.from(r.code_hash),
|
|
321
|
+
teeMeasurement: Uint8Array.from(r.tee_measurement),
|
|
322
|
+
modelFamily: r.model_family,
|
|
323
|
+
issuedAt: num(r.issued_at),
|
|
324
|
+
expiresAt: num(r.expires_at),
|
|
325
|
+
});
|
|
326
|
+
/**
|
|
327
|
+
* The exact bytes the attester signs: the domain tag, the registry's program id, then the
|
|
328
|
+
* Borsh-encoded attestation (bonded_registry::attestation_message).
|
|
329
|
+
*/
|
|
330
|
+
export function attestationMessage(attestation) {
|
|
331
|
+
return Buffer.concat([
|
|
332
|
+
ATTESTATION_DOMAIN,
|
|
333
|
+
PROGRAM_IDS.registry.toBuffer(),
|
|
334
|
+
coders.registry.types.encode("Attestation", toRaw(attestation)),
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
export async function fetchRegistryConfig(connection) {
|
|
338
|
+
const info = await connection.getAccountInfo(address.registryConfig());
|
|
339
|
+
if (!info)
|
|
340
|
+
throw new Error("the registry isn't initialized on this cluster");
|
|
341
|
+
const c = coders.registry.accounts.decode("Config", info.data);
|
|
342
|
+
return { attester: c.attester, agentCount: big(c.agent_count) };
|
|
343
|
+
}
|
|
344
|
+
export async function fetchAgent(connection, agentId) {
|
|
345
|
+
const info = await connection.getAccountInfo(address.agent(agentId));
|
|
346
|
+
if (!info)
|
|
347
|
+
return null;
|
|
348
|
+
const a = coders.registry.accounts.decode("Agent", info.data);
|
|
349
|
+
return {
|
|
350
|
+
agentId: big(a.agent_id),
|
|
351
|
+
owner: a.owner,
|
|
352
|
+
uri: a.uri,
|
|
353
|
+
score: a.score ? fromRaw(a.score) : null,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/** Register the next agent (id = agent count + 1), owned by `owner`, who signs and pays. */
|
|
357
|
+
export function registerInstruction(owner, nextAgentId, uri) {
|
|
358
|
+
return new TransactionInstruction({
|
|
359
|
+
programId: PROGRAM_IDS.registry,
|
|
360
|
+
data: coders.registry.instruction.encode("register", { uri }),
|
|
361
|
+
keys: metas(bondedRegistryIdl, "register", {
|
|
362
|
+
config: address.registryConfig(),
|
|
363
|
+
agent: address.agent(nextAgentId),
|
|
364
|
+
owner,
|
|
365
|
+
system_program: SYSTEM_PROGRAM,
|
|
366
|
+
}),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Post a score: the Ed25519 check of the attester's signature over `attestationMessage`, then
|
|
371
|
+
* the registry instruction, which looks for that check just before it.
|
|
372
|
+
*/
|
|
373
|
+
export function postScoreInstructions(attestation, attester) {
|
|
374
|
+
const verify = Ed25519Program.createInstructionWithPrivateKey({
|
|
375
|
+
privateKey: attester.secretKey,
|
|
376
|
+
message: attestationMessage(attestation),
|
|
377
|
+
});
|
|
378
|
+
const post = new TransactionInstruction({
|
|
379
|
+
programId: PROGRAM_IDS.registry,
|
|
380
|
+
data: coders.registry.instruction.encode("post_score", { attestation: toRaw(attestation) }),
|
|
381
|
+
keys: metas(bondedRegistryIdl, "post_score", {
|
|
382
|
+
config: address.registryConfig(),
|
|
383
|
+
agent: address.agent(attestation.agentId),
|
|
384
|
+
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
385
|
+
}),
|
|
386
|
+
});
|
|
387
|
+
return [verify, post];
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bondedhq/solana",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "TypeScript client for the Bonded Solana programs: IDLs, addresses, account decoding and instructions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"bonded",
|
|
8
|
+
"solana",
|
|
9
|
+
"anchor",
|
|
10
|
+
"mandate-vault"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/ddboy19912/bonded/tree/main/packages/solana#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ddboy19912/bonded/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/ddboy19912/bonded.git",
|
|
19
|
+
"directory": "packages/solana"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"!dist/**/*.map",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@anchor-lang/core": "^1.2.0",
|
|
44
|
+
"@solana/web3.js": "^1.98.4",
|
|
45
|
+
"bn.js": "^5.2.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/bn.js": "^5.2.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"lint": "eslint src test",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"idls": "node scripts/gen-idls.mjs",
|
|
56
|
+
"idls:check": "node scripts/gen-idls.mjs --check"
|
|
57
|
+
}
|
|
6
58
|
}
|