@kairoguard/sdk 0.0.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.
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Embedded skill file templates shipped with the SDK.
3
+ * Generated by `kairo init` into .cursor/skills/kairo/.
4
+ * All backend URLs are intentionally omitted -- the SDK and CLI
5
+ * resolve the endpoint internally.
6
+ */
7
+ export const SKILL_MD = `---
8
+ name: kairo
9
+ description: Manage Kairo policy-enforced agent wallets. Use when creating wallets, setting transaction policies, checking vault status, minting policy receipts, or signing transactions through the Kairo SDK/CLI. Supports full wallet lifecycle: register API key -> create wallet (DKG) -> create policy -> bind -> vault provision -> mint receipt -> sign. Uses @kairo/sdk for non-custodial wallet creation (agent keeps secret share locally).
10
+ ---
11
+
12
+ # Kairo — Agent Wallet Management
13
+
14
+ ## Quick Reference
15
+
16
+ CLI: \`npx kairo <command>\`
17
+ SDK reference: \`.cursor/skills/kairo/references/sdk.md\`
18
+ API reference: \`.cursor/skills/kairo/references/api.md\`
19
+
20
+ ## Setup
21
+
22
+ Run the one-line installer (already done if you see this file):
23
+ \`\`\`bash
24
+ npx @kairo/sdk init <YOUR_API_KEY>
25
+ \`\`\`
26
+
27
+ The API key is stored in \`~/.kairo/config.json\`. All CLI commands read it automatically.
28
+
29
+ ## Common Workflows
30
+
31
+ ### Check API Health
32
+ \`\`\`bash
33
+ npx kairo health
34
+ \`\`\`
35
+
36
+ ### Create a Policy
37
+ \`\`\`bash
38
+ npx kairo policy-create --stable-id "my-policy" --allow "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"
39
+ \`\`\`
40
+ Then register the version:
41
+ \`\`\`bash
42
+ npx kairo policy-register --policy-id "0x..."
43
+ \`\`\`
44
+
45
+ ### Create Wallet (via SDK)
46
+ For wallet creation, use the Node.js SDK (handles DKG client-side):
47
+ \`\`\`typescript
48
+ import { KairoClient } from "@kairo/sdk";
49
+ const kairo = new KairoClient({ apiKey: process.env.KAIRO_API_KEY! });
50
+ const wallet = await kairo.createWallet({ curve: "secp256k1" });
51
+ \`\`\`
52
+ See \`.cursor/skills/kairo/references/sdk.md\` for full SDK docs.
53
+
54
+ ### Provision Wallet into Vault
55
+ Requires: policy version registered first.
56
+ \`\`\`bash
57
+ npx kairo vault-provision --wallet-id "0x..." --policy-id "0x..." --stable-id "my-policy"
58
+ \`\`\`
59
+
60
+ ### Mint Policy Receipt
61
+ \`\`\`bash
62
+ npx kairo receipt-mint --policy-id "0x..." --binding-id "0x..." --destination "0x742d35Cc..." --intent-hash "0xabab..."
63
+ \`\`\`
64
+
65
+ ### Check Vault Status
66
+ \`\`\`bash
67
+ npx kairo vault-status --wallet-id "0x..."
68
+ \`\`\`
69
+
70
+ ### View Audit Events
71
+ \`\`\`bash
72
+ npx kairo audit --limit 20
73
+ \`\`\`
74
+
75
+ ## Full Agent Flow (End to End)
76
+
77
+ 1. \`npx @kairo/sdk init <key>\` — store API key, install skill
78
+ 2. \`npx kairo policy-create\` — create transaction policy with allowed addresses
79
+ 3. \`npx kairo policy-register\` — register version in on-chain registry
80
+ 4. Create wallet via SDK \`createWallet()\` — runs DKG locally, secret share stays on agent
81
+ 5. \`npx kairo vault-provision\` — bind policy + register wallet in vault (atomic)
82
+ 6. \`npx kairo receipt-mint\` — request policy check for a transaction
83
+ 7. Sign via SDK — both shares combine, only if policy allows
84
+
85
+ ## Trust Model
86
+
87
+ - Agent's key share stays local (\`~/.kairo/keys/\`)
88
+ - Server's key share stays on Kairo backend
89
+ - Neither party can sign alone
90
+ - Policy engine gates every transaction before server releases its share
91
+ - All policy decisions are on-chain (Sui) and verifiable
92
+
93
+ ## Troubleshooting
94
+
95
+ - **401 Unauthorized**: API key missing/invalid or not registered in backend key store. Re-run \`npx @kairo/sdk init <key>\` with a valid key.
96
+ - **403 Forbidden: key does not own wallet**: Wallet wasn't created/provisioned with this API key (ownership mismatch).
97
+ - **429 Rate limit**: Public Sui RPC throttled — use Shinami or own RPC provider.
98
+ - **MoveAbort code 102**: Policy version not registered — call \`npx kairo policy-register\` before \`vault-provision\`.
99
+ - **\`nonce too low\` / \`already known\`**: Rapid reruns or duplicate raw tx; wait for pending tx, then re-sign and rebroadcast.
100
+ - **AwaitingKeyHolderSignature**: Wallet needs activation after DKG — SDK activation flow required.
101
+ `;
102
+ export const API_REFERENCE_MD = `# Kairo API Reference
103
+
104
+ ## Authentication
105
+ All write endpoints require \`X-Kairo-Api-Key\` header.
106
+ The CLI reads the key from \`~/.kairo/config.json\` automatically.
107
+ Open endpoints: \`/health\`, \`/api/vault/info\`, \`/api/vault/status/:id\`, \`/api/audit/events\`
108
+
109
+ ## Key Registration
110
+ \`\`\`bash
111
+ npx kairo register --label "my-agent"
112
+ \`\`\`
113
+
114
+ ## Wallet Creation (via SDK)
115
+ The SDK handles DKG client-side. Agent keeps their secret share locally.
116
+ \`\`\`typescript
117
+ import { KairoClient } from "@kairo/sdk";
118
+ const kairo = new KairoClient({ apiKey: process.env.KAIRO_API_KEY! });
119
+ const wallet = await kairo.createWallet({ curve: "secp256k1" });
120
+ // wallet.walletId, wallet.address
121
+ \`\`\`
122
+
123
+ ## Policy Management
124
+
125
+ ### Create Policy
126
+ \`\`\`bash
127
+ npx kairo policy-create --stable-id "my-policy" --version "1.0.0" --allow "0x<address>"
128
+ \`\`\`
129
+
130
+ Rule types:
131
+ - \`1\` = MaxNativeValue (max single transaction value)
132
+ - \`10\` = PeriodLimit (cumulative spend limit per time window)
133
+
134
+ ### Register Policy Version
135
+ \`\`\`bash
136
+ npx kairo policy-register --policy-id "0x..."
137
+ \`\`\`
138
+
139
+ ### Get Policy Details
140
+ \`\`\`bash
141
+ npx kairo policy-details --policy-id "0x..."
142
+ \`\`\`
143
+
144
+ ## Vault
145
+
146
+ ### Provision (atomic binding + vault registration)
147
+ \`\`\`bash
148
+ npx kairo vault-provision --wallet-id "0x..." --policy-id "0x..." --stable-id "my-policy"
149
+ \`\`\`
150
+ Note: Register policy version BEFORE calling provision.
151
+
152
+ ### Check Status
153
+ \`\`\`bash
154
+ npx kairo vault-status --wallet-id "0x..."
155
+ \`\`\`
156
+
157
+ ## Receipt Minting
158
+ \`\`\`bash
159
+ npx kairo receipt-mint --policy-id "0x..." --binding-id "0x..." --destination "0x..." --intent-hash "0x..."
160
+ \`\`\`
161
+ Namespace: 1=EVM, 2=Bitcoin, 3=Solana
162
+
163
+ ## Utility
164
+ \`\`\`bash
165
+ npx kairo health # Server health
166
+ npx kairo audit --limit 20 # Recent audit events
167
+ \`\`\`
168
+ `;
169
+ export const SDK_REFERENCE_MD = `# Kairo SDK Reference
170
+
171
+ ## Installation
172
+ \`\`\`bash
173
+ npm install @kairo/sdk
174
+ \`\`\`
175
+
176
+ Requires: \`@ika.xyz/sdk\`, \`@mysten/sui\`
177
+
178
+ ## KairoClient
179
+
180
+ \`\`\`typescript
181
+ import { KairoClient } from "@kairo/sdk";
182
+
183
+ const kairo = new KairoClient({
184
+ apiKey: process.env.KAIRO_API_KEY!,
185
+ storePath: "~/.kairo/keys", // local secret share storage (default)
186
+ network: "testnet", // or "mainnet"
187
+ suiRpcUrl: "https://...", // optional, defaults to public testnet
188
+ });
189
+ \`\`\`
190
+
191
+ ### createWallet(opts?)
192
+ Creates a dWallet via client-side DKG. Secret share stays local.
193
+
194
+ \`\`\`typescript
195
+ const wallet = await kairo.createWallet({
196
+ curve: "secp256k1", // or "ed25519" for Solana
197
+ policyObjectId: "0x...", // optional: auto-provision into vault
198
+ stableId: "my-policy", // optional: binding label
199
+ });
200
+ // Returns: { walletId, address, curve, bindingObjectId?, createdAt }
201
+ \`\`\`
202
+
203
+ **Important:** If providing \`policyObjectId\`, register the policy version first.
204
+
205
+ ### listWallets()
206
+ Lists all wallets in local key store.
207
+ \`\`\`typescript
208
+ const wallets = kairo.listWallets();
209
+ \`\`\`
210
+
211
+ ### getWallet(walletId)
212
+ Gets a specific wallet from local store.
213
+ \`\`\`typescript
214
+ const w = kairo.getWallet("0x...");
215
+ \`\`\`
216
+
217
+ ## BackendClient (HTTP wrapper)
218
+ Lower-level HTTP client for direct API calls.
219
+
220
+ \`\`\`typescript
221
+ import { BackendClient } from "@kairo/sdk";
222
+
223
+ const client = new BackendClient({ apiKey: "your-key" });
224
+
225
+ await client.register("my-agent");
226
+ await client.getHealth();
227
+ await client.submitDKG({...});
228
+ await client.getDKGStatus(requestId);
229
+ await client.provision({...});
230
+ await client.mintReceipt({...});
231
+ \`\`\`
232
+
233
+ ## KeyStore (local storage)
234
+ File-based secret share storage at \`~/.kairo/keys/\`.
235
+
236
+ \`\`\`typescript
237
+ import { KeyStore } from "@kairo/sdk";
238
+
239
+ const store = new KeyStore("~/.kairo/keys");
240
+ store.save(record);
241
+ store.load("0x...");
242
+ store.list();
243
+ store.delete("0x...");
244
+ \`\`\`
245
+
246
+ ## Trust Model
247
+ - Agent's secret share -> stored locally (KeyStore), never sent to server
248
+ - Server's share -> held by Kairo backend
249
+ - Full signing -> requires BOTH shares + policy approval
250
+ - Kairo alone cannot sign (missing agent share)
251
+ - Agent alone cannot sign (missing server share)
252
+ `;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Solana Intent Utilities
3
+ *
4
+ * Client-side helpers for Solana transaction intent hashing and validation.
5
+ */
6
+ import type { Hex } from "./types.js";
7
+ /**
8
+ * Solana cluster types.
9
+ */
10
+ export type SolanaCluster = "mainnet-beta" | "devnet" | "testnet";
11
+ /**
12
+ * Lamports per SOL.
13
+ */
14
+ export declare const LAMPORTS_PER_SOL = 1000000000n;
15
+ /**
16
+ * Parsed Solana instruction.
17
+ */
18
+ export interface ParsedInstruction {
19
+ programId: string;
20
+ accounts: string[];
21
+ data: Uint8Array;
22
+ }
23
+ /**
24
+ * Parsed Solana transaction.
25
+ */
26
+ export interface ParsedSolanaTransaction {
27
+ feePayer: string;
28
+ recentBlockhash: string;
29
+ instructions: ParsedInstruction[];
30
+ programIds: string[];
31
+ }
32
+ /**
33
+ * Solana intent for policy verification.
34
+ */
35
+ export interface SolanaIntent {
36
+ cluster: SolanaCluster;
37
+ /** 32-byte intent hash (message hash) */
38
+ intentHash: Hex;
39
+ /** Fee payer (signer) */
40
+ feePayer: string;
41
+ /** Destination addresses (extracted from transfer instructions) */
42
+ destinations: string[];
43
+ /** Amounts in lamports */
44
+ amounts: bigint[];
45
+ /** Program IDs involved */
46
+ programIds: string[];
47
+ /** Raw transaction bytes (base58) */
48
+ transactionBase58: string;
49
+ }
50
+ /**
51
+ * Common Solana program IDs.
52
+ */
53
+ export declare const PROGRAM_IDS: {
54
+ readonly SYSTEM: "11111111111111111111111111111111";
55
+ readonly TOKEN: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
56
+ readonly TOKEN_2022: "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
57
+ readonly ASSOCIATED_TOKEN: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
58
+ readonly MEMO: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
59
+ readonly COMPUTE_BUDGET: "ComputeBudget111111111111111111111111111111";
60
+ };
61
+ /**
62
+ * System instruction types.
63
+ */
64
+ export declare enum SystemInstructionType {
65
+ CreateAccount = 0,
66
+ Assign = 1,
67
+ Transfer = 2,
68
+ CreateAccountWithSeed = 3,
69
+ AdvanceNonceAccount = 4,
70
+ WithdrawNonceAccount = 5,
71
+ InitializeNonceAccount = 6,
72
+ AuthorizeNonceAccount = 7,
73
+ Allocate = 8,
74
+ AllocateWithSeed = 9,
75
+ AssignWithSeed = 10,
76
+ TransferWithSeed = 11
77
+ }
78
+ /**
79
+ * Convert hex string to bytes.
80
+ */
81
+ export declare function hexToBytes(hex: string): Uint8Array;
82
+ /**
83
+ * Convert bytes to hex string.
84
+ */
85
+ export declare function bytesToHex(bytes: Uint8Array): Hex;
86
+ /**
87
+ * Decode base58 string to bytes.
88
+ */
89
+ export declare function base58Decode(str: string): Uint8Array;
90
+ /**
91
+ * Encode bytes to base58.
92
+ */
93
+ export declare function base58Encode(data: Uint8Array): string;
94
+ /**
95
+ * Validate a Solana address (base58 encoded Ed25519 public key).
96
+ */
97
+ export declare function validateSolanaAddress(address: string): boolean;
98
+ /**
99
+ * Compute intent hash from transaction message bytes.
100
+ */
101
+ export declare function computeSolanaIntentHash(messageBytes: Uint8Array): Uint8Array;
102
+ /**
103
+ * Check if a program ID is a known safe program.
104
+ */
105
+ export declare function isKnownSafeProgram(programId: string): boolean;
106
+ /**
107
+ * Check if a program ID is a token program.
108
+ */
109
+ export declare function isTokenProgram(programId: string): boolean;
110
+ /**
111
+ * Get human-readable name for a program.
112
+ */
113
+ export declare function getProgramName(programId: string): string;
114
+ /**
115
+ * Format lamports as SOL string.
116
+ */
117
+ export declare function lamportsToSOL(lamports: bigint): string;
118
+ /**
119
+ * Parse SOL string to lamports.
120
+ */
121
+ export declare function solToLamports(sol: string | number): bigint;
122
+ /**
123
+ * Extract transfer destinations and amounts from System Program instructions.
124
+ */
125
+ export declare function extractSystemTransfers(instructions: ParsedInstruction[]): {
126
+ destinations: string[];
127
+ amounts: bigint[];
128
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Solana Intent Utilities
3
+ *
4
+ * Client-side helpers for Solana transaction intent hashing and validation.
5
+ */
6
+ import { sha256 } from "@noble/hashes/sha256";
7
+ /**
8
+ * Lamports per SOL.
9
+ */
10
+ export const LAMPORTS_PER_SOL = 1000000000n;
11
+ /**
12
+ * Common Solana program IDs.
13
+ */
14
+ export const PROGRAM_IDS = {
15
+ SYSTEM: "11111111111111111111111111111111",
16
+ TOKEN: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
17
+ TOKEN_2022: "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
18
+ ASSOCIATED_TOKEN: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
19
+ MEMO: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr",
20
+ COMPUTE_BUDGET: "ComputeBudget111111111111111111111111111111",
21
+ };
22
+ /**
23
+ * System instruction types.
24
+ */
25
+ export var SystemInstructionType;
26
+ (function (SystemInstructionType) {
27
+ SystemInstructionType[SystemInstructionType["CreateAccount"] = 0] = "CreateAccount";
28
+ SystemInstructionType[SystemInstructionType["Assign"] = 1] = "Assign";
29
+ SystemInstructionType[SystemInstructionType["Transfer"] = 2] = "Transfer";
30
+ SystemInstructionType[SystemInstructionType["CreateAccountWithSeed"] = 3] = "CreateAccountWithSeed";
31
+ SystemInstructionType[SystemInstructionType["AdvanceNonceAccount"] = 4] = "AdvanceNonceAccount";
32
+ SystemInstructionType[SystemInstructionType["WithdrawNonceAccount"] = 5] = "WithdrawNonceAccount";
33
+ SystemInstructionType[SystemInstructionType["InitializeNonceAccount"] = 6] = "InitializeNonceAccount";
34
+ SystemInstructionType[SystemInstructionType["AuthorizeNonceAccount"] = 7] = "AuthorizeNonceAccount";
35
+ SystemInstructionType[SystemInstructionType["Allocate"] = 8] = "Allocate";
36
+ SystemInstructionType[SystemInstructionType["AllocateWithSeed"] = 9] = "AllocateWithSeed";
37
+ SystemInstructionType[SystemInstructionType["AssignWithSeed"] = 10] = "AssignWithSeed";
38
+ SystemInstructionType[SystemInstructionType["TransferWithSeed"] = 11] = "TransferWithSeed";
39
+ })(SystemInstructionType || (SystemInstructionType = {}));
40
+ /**
41
+ * Convert hex string to bytes.
42
+ */
43
+ export function hexToBytes(hex) {
44
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
45
+ const bytes = new Uint8Array(cleanHex.length / 2);
46
+ for (let i = 0; i < bytes.length; i++) {
47
+ bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
48
+ }
49
+ return bytes;
50
+ }
51
+ /**
52
+ * Convert bytes to hex string.
53
+ */
54
+ export function bytesToHex(bytes) {
55
+ return `0x${Array.from(bytes)
56
+ .map((b) => b.toString(16).padStart(2, "0"))
57
+ .join("")}`;
58
+ }
59
+ /**
60
+ * Base58 alphabet (Bitcoin/Solana variant).
61
+ */
62
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
63
+ /**
64
+ * Decode base58 string to bytes.
65
+ */
66
+ export function base58Decode(str) {
67
+ const bytes = [];
68
+ for (const char of str) {
69
+ const index = BASE58_ALPHABET.indexOf(char);
70
+ if (index === -1)
71
+ throw new Error(`Invalid base58 character: ${char}`);
72
+ let carry = index;
73
+ for (let i = 0; i < bytes.length; i++) {
74
+ carry += bytes[i] * 58;
75
+ bytes[i] = carry & 0xff;
76
+ carry >>= 8;
77
+ }
78
+ while (carry > 0) {
79
+ bytes.push(carry & 0xff);
80
+ carry >>= 8;
81
+ }
82
+ }
83
+ for (const char of str) {
84
+ if (char !== "1")
85
+ break;
86
+ bytes.push(0);
87
+ }
88
+ return Uint8Array.from(bytes.reverse());
89
+ }
90
+ /**
91
+ * Encode bytes to base58.
92
+ */
93
+ export function base58Encode(data) {
94
+ const digits = [0];
95
+ for (const byte of data) {
96
+ let carry = byte;
97
+ for (let j = 0; j < digits.length; j++) {
98
+ carry += digits[j] << 8;
99
+ digits[j] = carry % 58;
100
+ carry = Math.floor(carry / 58);
101
+ }
102
+ while (carry > 0) {
103
+ digits.push(carry % 58);
104
+ carry = Math.floor(carry / 58);
105
+ }
106
+ }
107
+ let result = "";
108
+ for (const byte of data) {
109
+ if (byte !== 0)
110
+ break;
111
+ result += "1";
112
+ }
113
+ for (let i = digits.length - 1; i >= 0; i--) {
114
+ result += BASE58_ALPHABET[digits[i]];
115
+ }
116
+ return result;
117
+ }
118
+ /**
119
+ * Validate a Solana address (base58 encoded Ed25519 public key).
120
+ */
121
+ export function validateSolanaAddress(address) {
122
+ try {
123
+ const decoded = base58Decode(address);
124
+ return decoded.length === 32;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * Compute intent hash from transaction message bytes.
132
+ */
133
+ export function computeSolanaIntentHash(messageBytes) {
134
+ return sha256(messageBytes);
135
+ }
136
+ /**
137
+ * Check if a program ID is a known safe program.
138
+ */
139
+ export function isKnownSafeProgram(programId) {
140
+ return (programId === PROGRAM_IDS.SYSTEM ||
141
+ programId === PROGRAM_IDS.MEMO ||
142
+ programId === PROGRAM_IDS.COMPUTE_BUDGET);
143
+ }
144
+ /**
145
+ * Check if a program ID is a token program.
146
+ */
147
+ export function isTokenProgram(programId) {
148
+ return (programId === PROGRAM_IDS.TOKEN ||
149
+ programId === PROGRAM_IDS.TOKEN_2022 ||
150
+ programId === PROGRAM_IDS.ASSOCIATED_TOKEN);
151
+ }
152
+ /**
153
+ * Get human-readable name for a program.
154
+ */
155
+ export function getProgramName(programId) {
156
+ switch (programId) {
157
+ case PROGRAM_IDS.SYSTEM:
158
+ return "System Program";
159
+ case PROGRAM_IDS.TOKEN:
160
+ return "Token Program";
161
+ case PROGRAM_IDS.TOKEN_2022:
162
+ return "Token 2022 Program";
163
+ case PROGRAM_IDS.ASSOCIATED_TOKEN:
164
+ return "Associated Token Program";
165
+ case PROGRAM_IDS.MEMO:
166
+ return "Memo Program";
167
+ case PROGRAM_IDS.COMPUTE_BUDGET:
168
+ return "Compute Budget Program";
169
+ default:
170
+ return `Unknown (${programId.slice(0, 8)}...)`;
171
+ }
172
+ }
173
+ /**
174
+ * Format lamports as SOL string.
175
+ */
176
+ export function lamportsToSOL(lamports) {
177
+ const sol = Number(lamports) / Number(LAMPORTS_PER_SOL);
178
+ return sol.toFixed(9);
179
+ }
180
+ /**
181
+ * Parse SOL string to lamports.
182
+ */
183
+ export function solToLamports(sol) {
184
+ const value = typeof sol === "string" ? parseFloat(sol) : sol;
185
+ return BigInt(Math.round(value * Number(LAMPORTS_PER_SOL)));
186
+ }
187
+ /**
188
+ * Extract transfer destinations and amounts from System Program instructions.
189
+ */
190
+ export function extractSystemTransfers(instructions) {
191
+ const destinations = [];
192
+ const amounts = [];
193
+ for (const ix of instructions) {
194
+ if (ix.programId !== PROGRAM_IDS.SYSTEM)
195
+ continue;
196
+ if (ix.data.length < 12)
197
+ continue;
198
+ // Check instruction type (first 4 bytes, little-endian)
199
+ const instructionType = ix.data[0] | (ix.data[1] << 8) | (ix.data[2] << 16) | (ix.data[3] << 24);
200
+ if (instructionType === SystemInstructionType.Transfer) {
201
+ // Next 8 bytes are lamports (little-endian u64)
202
+ let lamports = 0n;
203
+ for (let i = 0; i < 8; i++) {
204
+ lamports |= BigInt(ix.data[4 + i]) << BigInt(i * 8);
205
+ }
206
+ // Destination is the second account
207
+ if (ix.accounts.length >= 2) {
208
+ destinations.push(ix.accounts[1]);
209
+ amounts.push(lamports);
210
+ }
211
+ }
212
+ }
213
+ return { destinations, amounts };
214
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Verify a custody event hash by recomputing the canonical BCS encoding (v2/v3 hashing).
3
+ *
4
+ * Note: This does NOT discover events. You must provide a specific `CustodyEvent` object id.
5
+ */
6
+ export declare function fetchAndVerifyCustodyEvent(args: {
7
+ suiRpcUrl: string;
8
+ custodyEventObjectId: string;
9
+ }): Promise<{
10
+ ok: true;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ }>;