lightnode-sdk 0.4.9 → 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/dist/bridge.d.ts +233 -0
- package/dist/bridge.js +201 -0
- package/dist/chat.d.ts +70 -0
- package/dist/chat.js +88 -0
- package/dist/cli.js +105 -2
- package/dist/dao.d.ts +439 -0
- package/dist/dao.js +234 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +60 -3
- package/dist/onchain-models.d.ts +380 -0
- package/dist/onchain-models.js +187 -0
- package/dist/subgraph.d.ts +2 -0
- package/dist/subgraph.js +6 -0
- package/dist/worker.d.ts +104 -0
- package/dist/worker.js +186 -0
- package/package.json +1 -1
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge SDK: typed wrapper around the LightChain bridge (Hyperlane Warp
|
|
3
|
+
* Route). Bridging LCAI between Ethereum mainnet and LightChain mainnet
|
|
4
|
+
* (chain 9200) is the main flow today; the addresses below were extracted
|
|
5
|
+
* from `lightchain-protocol/bridge-ui/src/consts/warpRoutes.ts`.
|
|
6
|
+
*
|
|
7
|
+
* The protocol:
|
|
8
|
+
*
|
|
9
|
+
* Ethereum (chain 1) ─┐ ┌─ LightChain (chain 9200)
|
|
10
|
+
* LCAI ERC-20 │ │ native LCAI
|
|
11
|
+
* 0x9cA8...8927 │ │
|
|
12
|
+
* │ │
|
|
13
|
+
* user.approve() │ transferRemote(9200, recipient, amt) │
|
|
14
|
+
* ───────────► HypERC20Collateral 0x01f80b...e353 ──────────┼───► HypNative 0xEc7096...A6f1
|
|
15
|
+
* │ (locks LCAI in collateral vault) │ (mints / releases native LCAI)
|
|
16
|
+
* └────────────────────────────────────────┘
|
|
17
|
+
*
|
|
18
|
+
* Reverse: user calls transferRemote on HypNative (with native value =
|
|
19
|
+
* amount + quoteGasPayment) to send LCAI back to Ethereum.
|
|
20
|
+
*
|
|
21
|
+
* Both sides expose:
|
|
22
|
+
* - transferRemote(uint32 destination, bytes32 recipient, uint256 amount)
|
|
23
|
+
* payable returns (bytes32 messageId)
|
|
24
|
+
* - quoteGasPayment(uint32 destination) view returns (uint256)
|
|
25
|
+
*
|
|
26
|
+
* For the Ethereum -> LightChain direction the user must first call
|
|
27
|
+
* `approve(0x01f80b...e353, amount)` on the LCAI ERC-20.
|
|
28
|
+
*/
|
|
29
|
+
export type BridgeChain = "ethereum" | "lightchain-mainnet";
|
|
30
|
+
export interface BridgeEndpoints {
|
|
31
|
+
/** Numeric chain id (mainnet ETH = 1, LightChain mainnet = 9200). */
|
|
32
|
+
chainId: number;
|
|
33
|
+
/** Hyperlane domain id (matches chainId for these two routes). */
|
|
34
|
+
hyperlaneDomain: number;
|
|
35
|
+
/** The user-facing router (HypERC20Collateral on Ethereum, HypNative on LightChain). */
|
|
36
|
+
router: `0x${string}`;
|
|
37
|
+
/**
|
|
38
|
+
* Underlying ERC-20 the router collateralizes. Null on the synthetic
|
|
39
|
+
* side (LightChain mainnet uses native LCAI). On Ethereum this is the
|
|
40
|
+
* LCAI ERC-20 the user must `approve` before calling `transferRemote`.
|
|
41
|
+
*/
|
|
42
|
+
underlying: `0x${string}` | null;
|
|
43
|
+
/** Hyperlane mailbox (the message dispatch contract; useful for tracking). */
|
|
44
|
+
mailbox: `0x${string}`;
|
|
45
|
+
/** Block explorer for this side. */
|
|
46
|
+
explorer: string;
|
|
47
|
+
/** RPC endpoint we know about. */
|
|
48
|
+
rpc: string;
|
|
49
|
+
/** Human label for logs. */
|
|
50
|
+
label: string;
|
|
51
|
+
}
|
|
52
|
+
/** Live LCAI mainnet bridge route. From bridge-ui/src/consts/warpRoutes.ts. */
|
|
53
|
+
export declare const BRIDGE_ROUTE: Record<BridgeChain, BridgeEndpoints>;
|
|
54
|
+
/** Hyperlane TokenRouter ABI (subset we use). */
|
|
55
|
+
export declare const HYPERLANE_ROUTER_ABI: readonly [{
|
|
56
|
+
readonly name: "transferRemote";
|
|
57
|
+
readonly type: "function";
|
|
58
|
+
readonly stateMutability: "payable";
|
|
59
|
+
readonly inputs: readonly [{
|
|
60
|
+
readonly type: "uint32";
|
|
61
|
+
readonly name: "destination";
|
|
62
|
+
}, {
|
|
63
|
+
readonly type: "bytes32";
|
|
64
|
+
readonly name: "recipient";
|
|
65
|
+
}, {
|
|
66
|
+
readonly type: "uint256";
|
|
67
|
+
readonly name: "amount";
|
|
68
|
+
}];
|
|
69
|
+
readonly outputs: readonly [{
|
|
70
|
+
readonly type: "bytes32";
|
|
71
|
+
readonly name: "messageId";
|
|
72
|
+
}];
|
|
73
|
+
}, {
|
|
74
|
+
readonly name: "quoteGasPayment";
|
|
75
|
+
readonly type: "function";
|
|
76
|
+
readonly stateMutability: "view";
|
|
77
|
+
readonly inputs: readonly [{
|
|
78
|
+
readonly type: "uint32";
|
|
79
|
+
readonly name: "destination";
|
|
80
|
+
}];
|
|
81
|
+
readonly outputs: readonly [{
|
|
82
|
+
readonly type: "uint256";
|
|
83
|
+
}];
|
|
84
|
+
}, {
|
|
85
|
+
readonly name: "balanceOf";
|
|
86
|
+
readonly type: "function";
|
|
87
|
+
readonly stateMutability: "view";
|
|
88
|
+
readonly inputs: readonly [{
|
|
89
|
+
readonly type: "address";
|
|
90
|
+
readonly name: "account";
|
|
91
|
+
}];
|
|
92
|
+
readonly outputs: readonly [{
|
|
93
|
+
readonly type: "uint256";
|
|
94
|
+
}];
|
|
95
|
+
}];
|
|
96
|
+
/** Minimal ERC-20 ABI for the LCAI approval step on the Ethereum side. */
|
|
97
|
+
export declare const ERC20_ABI: readonly [{
|
|
98
|
+
readonly name: "approve";
|
|
99
|
+
readonly type: "function";
|
|
100
|
+
readonly stateMutability: "nonpayable";
|
|
101
|
+
readonly inputs: readonly [{
|
|
102
|
+
readonly type: "address";
|
|
103
|
+
readonly name: "spender";
|
|
104
|
+
}, {
|
|
105
|
+
readonly type: "uint256";
|
|
106
|
+
readonly name: "amount";
|
|
107
|
+
}];
|
|
108
|
+
readonly outputs: readonly [{
|
|
109
|
+
readonly type: "bool";
|
|
110
|
+
}];
|
|
111
|
+
}, {
|
|
112
|
+
readonly name: "allowance";
|
|
113
|
+
readonly type: "function";
|
|
114
|
+
readonly stateMutability: "view";
|
|
115
|
+
readonly inputs: readonly [{
|
|
116
|
+
readonly type: "address";
|
|
117
|
+
readonly name: "owner";
|
|
118
|
+
}, {
|
|
119
|
+
readonly type: "address";
|
|
120
|
+
readonly name: "spender";
|
|
121
|
+
}];
|
|
122
|
+
readonly outputs: readonly [{
|
|
123
|
+
readonly type: "uint256";
|
|
124
|
+
}];
|
|
125
|
+
}, {
|
|
126
|
+
readonly name: "balanceOf";
|
|
127
|
+
readonly type: "function";
|
|
128
|
+
readonly stateMutability: "view";
|
|
129
|
+
readonly inputs: readonly [{
|
|
130
|
+
readonly type: "address";
|
|
131
|
+
readonly name: "account";
|
|
132
|
+
}];
|
|
133
|
+
readonly outputs: readonly [{
|
|
134
|
+
readonly type: "uint256";
|
|
135
|
+
}];
|
|
136
|
+
}, {
|
|
137
|
+
readonly name: "decimals";
|
|
138
|
+
readonly type: "function";
|
|
139
|
+
readonly stateMutability: "view";
|
|
140
|
+
readonly inputs: readonly [];
|
|
141
|
+
readonly outputs: readonly [{
|
|
142
|
+
readonly type: "uint8";
|
|
143
|
+
}];
|
|
144
|
+
}];
|
|
145
|
+
/** Pad a 20-byte EVM address to a 32-byte (bytes32) Hyperlane recipient. */
|
|
146
|
+
export declare function addressToBytes32(addr: `0x${string}`): `0x${string}`;
|
|
147
|
+
interface MinimalPublicClient {
|
|
148
|
+
readContract: (args: {
|
|
149
|
+
address: `0x${string}`;
|
|
150
|
+
abi: readonly unknown[];
|
|
151
|
+
functionName: string;
|
|
152
|
+
args?: readonly unknown[];
|
|
153
|
+
}) => Promise<unknown>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the Hyperlane gas-payment quote for delivering ONE bridge message
|
|
157
|
+
* from `from` to `to`. Returned in wei of the FROM chain's gas token
|
|
158
|
+
* (ETH on Ethereum, LCAI on LightChain). Add this to the `value` of the
|
|
159
|
+
* `transferRemote` call.
|
|
160
|
+
*/
|
|
161
|
+
export declare function quoteBridgeFee(client: MinimalPublicClient, from: BridgeChain, to: BridgeChain): Promise<bigint>;
|
|
162
|
+
/**
|
|
163
|
+
* Read the underlying token balance of `account` on the FROM side. Returned
|
|
164
|
+
* in raw wei (18 decimals for LCAI on both sides). For Ethereum -> LightChain
|
|
165
|
+
* use `from: "ethereum"` (returns ERC-20 balance). For the reverse use
|
|
166
|
+
* `from: "lightchain-mainnet"` and pass the chain's native-balance reader
|
|
167
|
+
* separately - HypNative does not expose `balanceOf`.
|
|
168
|
+
*/
|
|
169
|
+
export declare function bridgeableBalance(client: MinimalPublicClient, from: BridgeChain, account: `0x${string}`): Promise<bigint>;
|
|
170
|
+
/** Read the LCAI ERC-20 allowance the user has approved for the bridge router. */
|
|
171
|
+
export declare function bridgeAllowance(client: MinimalPublicClient, account: `0x${string}`): Promise<bigint>;
|
|
172
|
+
interface MinimalWalletClient {
|
|
173
|
+
writeContract: (args: {
|
|
174
|
+
address: `0x${string}`;
|
|
175
|
+
abi: readonly unknown[];
|
|
176
|
+
functionName: string;
|
|
177
|
+
args: readonly unknown[];
|
|
178
|
+
value?: bigint;
|
|
179
|
+
gas?: bigint;
|
|
180
|
+
}) => Promise<`0x${string}`>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Approve the Ethereum bridge router to spend LCAI on the user's behalf.
|
|
184
|
+
* Required ONCE before the first Ethereum -> LightChain transfer. The
|
|
185
|
+
* standard pattern is to approve `MaxUint256` so subsequent transfers do
|
|
186
|
+
* not need a second approve. Returns the tx hash.
|
|
187
|
+
*/
|
|
188
|
+
export declare function approveBridge(wallet: MinimalWalletClient, amount?: bigint): Promise<`0x${string}`>;
|
|
189
|
+
export interface BridgeTransferArgs {
|
|
190
|
+
/** Source chain. */
|
|
191
|
+
from: BridgeChain;
|
|
192
|
+
/** Destination chain. */
|
|
193
|
+
to: BridgeChain;
|
|
194
|
+
/** Amount to bridge in raw wei (18 decimals for LCAI on both sides). */
|
|
195
|
+
amount: bigint;
|
|
196
|
+
/** Recipient EVM address on the destination chain. Defaults to the signer's address. */
|
|
197
|
+
recipient: `0x${string}`;
|
|
198
|
+
/**
|
|
199
|
+
* Bridge fee in wei to attach as `msg.value`. Get from `quoteBridgeFee()`.
|
|
200
|
+
* On the LightChain side (HypNative) the SDK adds the `amount` to this so
|
|
201
|
+
* the total `value` passed to transferRemote equals `amount + fee`.
|
|
202
|
+
*/
|
|
203
|
+
fee: bigint;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Build and send the bridge transferRemote call. For Ethereum -> LightChain,
|
|
207
|
+
* `approveBridge()` must have run first. For LightChain -> Ethereum, no
|
|
208
|
+
* approval is needed (native LCAI is attached as value).
|
|
209
|
+
*/
|
|
210
|
+
export declare function bridgeTransfer(wallet: MinimalWalletClient, args: BridgeTransferArgs): Promise<`0x${string}`>;
|
|
211
|
+
/**
|
|
212
|
+
* Convenience wrapper that bundles read + write helpers and exposes the
|
|
213
|
+
* mainnet route addresses as fields. Pass a viem PublicClient for reads
|
|
214
|
+
* and (optionally) a WalletClient for writes.
|
|
215
|
+
*/
|
|
216
|
+
export declare class Bridge {
|
|
217
|
+
private readonly publicClient;
|
|
218
|
+
private readonly walletClient?;
|
|
219
|
+
/** Confirmed mainnet route. Currently the only live bridge. */
|
|
220
|
+
readonly route: Record<BridgeChain, BridgeEndpoints>;
|
|
221
|
+
constructor(publicClient: MinimalPublicClient, walletClient?: MinimalWalletClient | undefined);
|
|
222
|
+
/** See `quoteBridgeFee` standalone. */
|
|
223
|
+
quoteFee(from: BridgeChain, to: BridgeChain): Promise<bigint>;
|
|
224
|
+
/** See `bridgeableBalance` standalone. */
|
|
225
|
+
balance(from: BridgeChain, account: `0x${string}`): Promise<bigint>;
|
|
226
|
+
/** See `bridgeAllowance` standalone (Ethereum side only). */
|
|
227
|
+
allowance(account: `0x${string}`): Promise<bigint>;
|
|
228
|
+
/** Approve the Ethereum bridge router. Requires a wallet. */
|
|
229
|
+
approve(amount?: bigint): Promise<`0x${string}`>;
|
|
230
|
+
/** Send a bridge transfer. Requires a wallet. */
|
|
231
|
+
transfer(args: BridgeTransferArgs): Promise<`0x${string}`>;
|
|
232
|
+
}
|
|
233
|
+
export {};
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge SDK: typed wrapper around the LightChain bridge (Hyperlane Warp
|
|
3
|
+
* Route). Bridging LCAI between Ethereum mainnet and LightChain mainnet
|
|
4
|
+
* (chain 9200) is the main flow today; the addresses below were extracted
|
|
5
|
+
* from `lightchain-protocol/bridge-ui/src/consts/warpRoutes.ts`.
|
|
6
|
+
*
|
|
7
|
+
* The protocol:
|
|
8
|
+
*
|
|
9
|
+
* Ethereum (chain 1) ─┐ ┌─ LightChain (chain 9200)
|
|
10
|
+
* LCAI ERC-20 │ │ native LCAI
|
|
11
|
+
* 0x9cA8...8927 │ │
|
|
12
|
+
* │ │
|
|
13
|
+
* user.approve() │ transferRemote(9200, recipient, amt) │
|
|
14
|
+
* ───────────► HypERC20Collateral 0x01f80b...e353 ──────────┼───► HypNative 0xEc7096...A6f1
|
|
15
|
+
* │ (locks LCAI in collateral vault) │ (mints / releases native LCAI)
|
|
16
|
+
* └────────────────────────────────────────┘
|
|
17
|
+
*
|
|
18
|
+
* Reverse: user calls transferRemote on HypNative (with native value =
|
|
19
|
+
* amount + quoteGasPayment) to send LCAI back to Ethereum.
|
|
20
|
+
*
|
|
21
|
+
* Both sides expose:
|
|
22
|
+
* - transferRemote(uint32 destination, bytes32 recipient, uint256 amount)
|
|
23
|
+
* payable returns (bytes32 messageId)
|
|
24
|
+
* - quoteGasPayment(uint32 destination) view returns (uint256)
|
|
25
|
+
*
|
|
26
|
+
* For the Ethereum -> LightChain direction the user must first call
|
|
27
|
+
* `approve(0x01f80b...e353, amount)` on the LCAI ERC-20.
|
|
28
|
+
*/
|
|
29
|
+
import { parseAbi } from "viem";
|
|
30
|
+
/** Live LCAI mainnet bridge route. From bridge-ui/src/consts/warpRoutes.ts. */
|
|
31
|
+
export const BRIDGE_ROUTE = {
|
|
32
|
+
ethereum: {
|
|
33
|
+
chainId: 1,
|
|
34
|
+
hyperlaneDomain: 1,
|
|
35
|
+
router: "0x01f80bb8e78e79881E8Ec7832fB6C2c59f64e353",
|
|
36
|
+
underlying: "0x9cA8530CA349c966Fe9ef903Df17a75B8A778927", // LCAI ERC-20
|
|
37
|
+
mailbox: "0x287cf56E5b1435Ae59BF9Ce6443F055A0321a063",
|
|
38
|
+
explorer: "https://etherscan.io",
|
|
39
|
+
rpc: "https://eth.llamarpc.com",
|
|
40
|
+
label: "Ethereum",
|
|
41
|
+
},
|
|
42
|
+
"lightchain-mainnet": {
|
|
43
|
+
chainId: 9200,
|
|
44
|
+
hyperlaneDomain: 9200,
|
|
45
|
+
router: "0xEc7096A3116EE769457C939617375Ec1785AA6f1",
|
|
46
|
+
underlying: null, // HypNative: amount IS the native LCAI value
|
|
47
|
+
mailbox: "0x142a9CEf00ACcAddB76283c49A1Bf37f20c1F00e",
|
|
48
|
+
explorer: "https://mainnet.lightscan.app",
|
|
49
|
+
rpc: "https://rpc.mainnet.lightchain.ai",
|
|
50
|
+
label: "LightChain mainnet",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
/** Hyperlane TokenRouter ABI (subset we use). */
|
|
54
|
+
export const HYPERLANE_ROUTER_ABI = parseAbi([
|
|
55
|
+
"function transferRemote(uint32 destination, bytes32 recipient, uint256 amount) external payable returns (bytes32 messageId)",
|
|
56
|
+
"function quoteGasPayment(uint32 destination) external view returns (uint256)",
|
|
57
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
58
|
+
]);
|
|
59
|
+
/** Minimal ERC-20 ABI for the LCAI approval step on the Ethereum side. */
|
|
60
|
+
export const ERC20_ABI = parseAbi([
|
|
61
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
62
|
+
"function allowance(address owner, address spender) external view returns (uint256)",
|
|
63
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
64
|
+
"function decimals() external view returns (uint8)",
|
|
65
|
+
]);
|
|
66
|
+
/** Pad a 20-byte EVM address to a 32-byte (bytes32) Hyperlane recipient. */
|
|
67
|
+
export function addressToBytes32(addr) {
|
|
68
|
+
const hex = addr.toLowerCase().replace(/^0x/, "");
|
|
69
|
+
if (hex.length !== 40)
|
|
70
|
+
throw new Error("bridge: recipient must be a 20-byte EVM address");
|
|
71
|
+
return (`0x${"0".repeat(24)}${hex}`);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the Hyperlane gas-payment quote for delivering ONE bridge message
|
|
75
|
+
* from `from` to `to`. Returned in wei of the FROM chain's gas token
|
|
76
|
+
* (ETH on Ethereum, LCAI on LightChain). Add this to the `value` of the
|
|
77
|
+
* `transferRemote` call.
|
|
78
|
+
*/
|
|
79
|
+
export async function quoteBridgeFee(client, from, to) {
|
|
80
|
+
if (from === to)
|
|
81
|
+
throw new Error("bridge: source and destination must differ");
|
|
82
|
+
const src = BRIDGE_ROUTE[from];
|
|
83
|
+
const dst = BRIDGE_ROUTE[to];
|
|
84
|
+
const result = (await client.readContract({
|
|
85
|
+
address: src.router,
|
|
86
|
+
abi: HYPERLANE_ROUTER_ABI,
|
|
87
|
+
functionName: "quoteGasPayment",
|
|
88
|
+
args: [dst.hyperlaneDomain],
|
|
89
|
+
}));
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read the underlying token balance of `account` on the FROM side. Returned
|
|
94
|
+
* in raw wei (18 decimals for LCAI on both sides). For Ethereum -> LightChain
|
|
95
|
+
* use `from: "ethereum"` (returns ERC-20 balance). For the reverse use
|
|
96
|
+
* `from: "lightchain-mainnet"` and pass the chain's native-balance reader
|
|
97
|
+
* separately - HypNative does not expose `balanceOf`.
|
|
98
|
+
*/
|
|
99
|
+
export async function bridgeableBalance(client, from, account) {
|
|
100
|
+
const side = BRIDGE_ROUTE[from];
|
|
101
|
+
if (!side.underlying) {
|
|
102
|
+
throw new Error(`bridge: ${from} bridges native LCAI; query getBalance(account) on the RPC directly`);
|
|
103
|
+
}
|
|
104
|
+
return (await client.readContract({
|
|
105
|
+
address: side.underlying,
|
|
106
|
+
abi: ERC20_ABI,
|
|
107
|
+
functionName: "balanceOf",
|
|
108
|
+
args: [account],
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
/** Read the LCAI ERC-20 allowance the user has approved for the bridge router. */
|
|
112
|
+
export async function bridgeAllowance(client, account) {
|
|
113
|
+
const eth = BRIDGE_ROUTE.ethereum;
|
|
114
|
+
if (!eth.underlying)
|
|
115
|
+
throw new Error("bridge: unreachable - Ethereum side has no underlying");
|
|
116
|
+
return (await client.readContract({
|
|
117
|
+
address: eth.underlying,
|
|
118
|
+
abi: ERC20_ABI,
|
|
119
|
+
functionName: "allowance",
|
|
120
|
+
args: [account, eth.router],
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Approve the Ethereum bridge router to spend LCAI on the user's behalf.
|
|
125
|
+
* Required ONCE before the first Ethereum -> LightChain transfer. The
|
|
126
|
+
* standard pattern is to approve `MaxUint256` so subsequent transfers do
|
|
127
|
+
* not need a second approve. Returns the tx hash.
|
|
128
|
+
*/
|
|
129
|
+
export async function approveBridge(wallet, amount = (1n << 256n) - 1n) {
|
|
130
|
+
const eth = BRIDGE_ROUTE.ethereum;
|
|
131
|
+
if (!eth.underlying)
|
|
132
|
+
throw new Error("bridge: unreachable");
|
|
133
|
+
return wallet.writeContract({
|
|
134
|
+
address: eth.underlying,
|
|
135
|
+
abi: ERC20_ABI,
|
|
136
|
+
functionName: "approve",
|
|
137
|
+
args: [eth.router, amount],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Build and send the bridge transferRemote call. For Ethereum -> LightChain,
|
|
142
|
+
* `approveBridge()` must have run first. For LightChain -> Ethereum, no
|
|
143
|
+
* approval is needed (native LCAI is attached as value).
|
|
144
|
+
*/
|
|
145
|
+
export async function bridgeTransfer(wallet, args) {
|
|
146
|
+
if (args.from === args.to)
|
|
147
|
+
throw new Error("bridge: source and destination must differ");
|
|
148
|
+
const src = BRIDGE_ROUTE[args.from];
|
|
149
|
+
const dst = BRIDGE_ROUTE[args.to];
|
|
150
|
+
// HypNative requires `value = amount + fee`. HypERC20Collateral takes the
|
|
151
|
+
// ERC-20 from the user's allowance and only requires the fee as `value`.
|
|
152
|
+
const value = src.underlying ? args.fee : args.amount + args.fee;
|
|
153
|
+
return wallet.writeContract({
|
|
154
|
+
address: src.router,
|
|
155
|
+
abi: HYPERLANE_ROUTER_ABI,
|
|
156
|
+
functionName: "transferRemote",
|
|
157
|
+
args: [dst.hyperlaneDomain, addressToBytes32(args.recipient), args.amount],
|
|
158
|
+
value,
|
|
159
|
+
gas: 500000n,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// LightNode-style facade so consumers can do `new Bridge()` and read fields.
|
|
164
|
+
// =============================================================================
|
|
165
|
+
/**
|
|
166
|
+
* Convenience wrapper that bundles read + write helpers and exposes the
|
|
167
|
+
* mainnet route addresses as fields. Pass a viem PublicClient for reads
|
|
168
|
+
* and (optionally) a WalletClient for writes.
|
|
169
|
+
*/
|
|
170
|
+
export class Bridge {
|
|
171
|
+
constructor(publicClient, walletClient) {
|
|
172
|
+
this.publicClient = publicClient;
|
|
173
|
+
this.walletClient = walletClient;
|
|
174
|
+
/** Confirmed mainnet route. Currently the only live bridge. */
|
|
175
|
+
this.route = BRIDGE_ROUTE;
|
|
176
|
+
}
|
|
177
|
+
/** See `quoteBridgeFee` standalone. */
|
|
178
|
+
quoteFee(from, to) {
|
|
179
|
+
return quoteBridgeFee(this.publicClient, from, to);
|
|
180
|
+
}
|
|
181
|
+
/** See `bridgeableBalance` standalone. */
|
|
182
|
+
balance(from, account) {
|
|
183
|
+
return bridgeableBalance(this.publicClient, from, account);
|
|
184
|
+
}
|
|
185
|
+
/** See `bridgeAllowance` standalone (Ethereum side only). */
|
|
186
|
+
allowance(account) {
|
|
187
|
+
return bridgeAllowance(this.publicClient, account);
|
|
188
|
+
}
|
|
189
|
+
/** Approve the Ethereum bridge router. Requires a wallet. */
|
|
190
|
+
approve(amount) {
|
|
191
|
+
if (!this.walletClient)
|
|
192
|
+
throw new Error("bridge: no wallet client; pass one to the Bridge constructor for writes");
|
|
193
|
+
return approveBridge(this.walletClient, amount);
|
|
194
|
+
}
|
|
195
|
+
/** Send a bridge transfer. Requires a wallet. */
|
|
196
|
+
transfer(args) {
|
|
197
|
+
if (!this.walletClient)
|
|
198
|
+
throw new Error("bridge: no wallet client; pass one to the Bridge constructor for writes");
|
|
199
|
+
return bridgeTransfer(this.walletClient, args);
|
|
200
|
+
}
|
|
201
|
+
}
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-turn conversation helper. The LightChain inference protocol is
|
|
3
|
+
* single-turn at the session level (one createSession + one submitJob =
|
|
4
|
+
* one answer), but stateful chat is the most common builder need. So this
|
|
5
|
+
* keeps the conversation HISTORY client-side, serializes it into a single
|
|
6
|
+
* prompt per turn, and runs one full encrypted inference per turn under
|
|
7
|
+
* the hood. To the protocol it looks like N independent jobs; to the
|
|
8
|
+
* caller it reads as a coherent chat.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
*
|
|
12
|
+
* const chat = new Conversation({ network: "testnet", privateKey: "0x..." });
|
|
13
|
+
* const a = await chat.send("Who wrote 'The Great Gatsby'?");
|
|
14
|
+
* const b = await chat.send("In what year?"); // 'b' sees the prior turn
|
|
15
|
+
* console.log(chat.messages()); // full transcript
|
|
16
|
+
*/
|
|
17
|
+
import { type RunInferenceWithKeyArgs, type RunInferenceResult } from "./inference.js";
|
|
18
|
+
export type ChatRole = "system" | "user" | "assistant";
|
|
19
|
+
export interface ChatMessage {
|
|
20
|
+
role: ChatRole;
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ConversationOptions extends Omit<RunInferenceWithKeyArgs, "prompt"> {
|
|
24
|
+
/**
|
|
25
|
+
* Initial system message (optional). Prepended to the serialized prompt on
|
|
26
|
+
* every turn. Use for persona, response constraints, or guardrails.
|
|
27
|
+
*/
|
|
28
|
+
system?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Cap how many prior turns are serialized into each new prompt. Older
|
|
31
|
+
* turns drop off in FIFO order. Default 20. Models tolerate longer
|
|
32
|
+
* histories but per-call fees scale with prompt length on token-priced
|
|
33
|
+
* networks.
|
|
34
|
+
*/
|
|
35
|
+
maxHistoryTurns?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ConversationSendResult extends RunInferenceResult {
|
|
38
|
+
/** Updated transcript after this turn (includes the latest user + assistant pair). */
|
|
39
|
+
messages: ChatMessage[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One round of `send` returns the assistant's reply plus all the
|
|
43
|
+
* on-chain receipts. `messages()` exposes the running transcript so a UI
|
|
44
|
+
* can render it; `reset()` clears history.
|
|
45
|
+
*/
|
|
46
|
+
export declare class Conversation {
|
|
47
|
+
private readonly opts;
|
|
48
|
+
private readonly history;
|
|
49
|
+
constructor(opts: ConversationOptions);
|
|
50
|
+
/** Read-only snapshot of the conversation so far. */
|
|
51
|
+
messages(): ChatMessage[];
|
|
52
|
+
/** Drop the running history (the next send becomes a fresh first turn). */
|
|
53
|
+
reset(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Push a single user message and run one full inference. Returns the
|
|
56
|
+
* assistant's reply plus the standard runInference result (txs, worker,
|
|
57
|
+
* jobId). The assistant's reply is automatically appended to history so
|
|
58
|
+
* the next send sees it.
|
|
59
|
+
*/
|
|
60
|
+
send(message: string): Promise<ConversationSendResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Format the current history as a single text prompt the model can read.
|
|
63
|
+
* Chat-style turn markers ("User:" / "Assistant:") since the protocol's
|
|
64
|
+
* llama3-8b serving stack treats prompts as raw text and any reasonable
|
|
65
|
+
* formatting works. Uses the configured max-history-turns cap.
|
|
66
|
+
*/
|
|
67
|
+
private serialize;
|
|
68
|
+
}
|
|
69
|
+
/** Functional shortcut for `new Conversation(opts)` so it reads inline. */
|
|
70
|
+
export declare function chat(opts: ConversationOptions): Conversation;
|
package/dist/chat.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-turn conversation helper. The LightChain inference protocol is
|
|
3
|
+
* single-turn at the session level (one createSession + one submitJob =
|
|
4
|
+
* one answer), but stateful chat is the most common builder need. So this
|
|
5
|
+
* keeps the conversation HISTORY client-side, serializes it into a single
|
|
6
|
+
* prompt per turn, and runs one full encrypted inference per turn under
|
|
7
|
+
* the hood. To the protocol it looks like N independent jobs; to the
|
|
8
|
+
* caller it reads as a coherent chat.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
*
|
|
12
|
+
* const chat = new Conversation({ network: "testnet", privateKey: "0x..." });
|
|
13
|
+
* const a = await chat.send("Who wrote 'The Great Gatsby'?");
|
|
14
|
+
* const b = await chat.send("In what year?"); // 'b' sees the prior turn
|
|
15
|
+
* console.log(chat.messages()); // full transcript
|
|
16
|
+
*/
|
|
17
|
+
import { runInferenceWithKey } from "./inference.js";
|
|
18
|
+
/**
|
|
19
|
+
* One round of `send` returns the assistant's reply plus all the
|
|
20
|
+
* on-chain receipts. `messages()` exposes the running transcript so a UI
|
|
21
|
+
* can render it; `reset()` clears history.
|
|
22
|
+
*/
|
|
23
|
+
export class Conversation {
|
|
24
|
+
constructor(opts) {
|
|
25
|
+
this.history = [];
|
|
26
|
+
if (!opts.network)
|
|
27
|
+
throw new Error("Conversation: network is required");
|
|
28
|
+
if (!opts.privateKey)
|
|
29
|
+
throw new Error("Conversation: privateKey is required");
|
|
30
|
+
this.opts = opts;
|
|
31
|
+
}
|
|
32
|
+
/** Read-only snapshot of the conversation so far. */
|
|
33
|
+
messages() {
|
|
34
|
+
return [...this.history];
|
|
35
|
+
}
|
|
36
|
+
/** Drop the running history (the next send becomes a fresh first turn). */
|
|
37
|
+
reset() {
|
|
38
|
+
this.history.length = 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Push a single user message and run one full inference. Returns the
|
|
42
|
+
* assistant's reply plus the standard runInference result (txs, worker,
|
|
43
|
+
* jobId). The assistant's reply is automatically appended to history so
|
|
44
|
+
* the next send sees it.
|
|
45
|
+
*/
|
|
46
|
+
async send(message) {
|
|
47
|
+
if (!message?.trim())
|
|
48
|
+
throw new Error("Conversation.send: message is empty");
|
|
49
|
+
// 1. Add the new user turn BEFORE serializing so the model sees it.
|
|
50
|
+
this.history.push({ role: "user", content: message });
|
|
51
|
+
// 2. Build the serialized prompt: system (if any) + last N turns.
|
|
52
|
+
const prompt = this.serialize();
|
|
53
|
+
// 3. Run one full encrypted inference with the conversation as prompt.
|
|
54
|
+
const result = await runInferenceWithKey({
|
|
55
|
+
...this.opts,
|
|
56
|
+
prompt,
|
|
57
|
+
});
|
|
58
|
+
// 4. Append the assistant's reply and return.
|
|
59
|
+
this.history.push({ role: "assistant", content: result.answer });
|
|
60
|
+
return { ...result, messages: this.messages() };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format the current history as a single text prompt the model can read.
|
|
64
|
+
* Chat-style turn markers ("User:" / "Assistant:") since the protocol's
|
|
65
|
+
* llama3-8b serving stack treats prompts as raw text and any reasonable
|
|
66
|
+
* formatting works. Uses the configured max-history-turns cap.
|
|
67
|
+
*/
|
|
68
|
+
serialize() {
|
|
69
|
+
const cap = this.opts.maxHistoryTurns ?? 20;
|
|
70
|
+
// A "turn" here is one message; cap*2 messages = cap user+assistant pairs.
|
|
71
|
+
const recent = this.history.slice(Math.max(0, this.history.length - cap * 2));
|
|
72
|
+
const sys = this.opts.system?.trim();
|
|
73
|
+
const lines = [];
|
|
74
|
+
if (sys)
|
|
75
|
+
lines.push(`System: ${sys}`);
|
|
76
|
+
for (const m of recent) {
|
|
77
|
+
const tag = m.role === "user" ? "User" : m.role === "assistant" ? "Assistant" : "System";
|
|
78
|
+
lines.push(`${tag}: ${m.content}`);
|
|
79
|
+
}
|
|
80
|
+
// Trailing prompt for the model to continue from.
|
|
81
|
+
lines.push("Assistant:");
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Functional shortcut for `new Conversation(opts)` so it reads inline. */
|
|
86
|
+
export function chat(opts) {
|
|
87
|
+
return new Conversation(opts);
|
|
88
|
+
}
|