@arbilink/sdk 0.1.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.
@@ -0,0 +1,211 @@
1
+ [
2
+ {
3
+ "type": "constructor",
4
+ "inputs": [
5
+ { "name": "_messageHub", "type": "address" },
6
+ { "name": "_hubSigningKey", "type": "address" }
7
+ ]
8
+ },
9
+ {
10
+ "type": "function",
11
+ "name": "receiveMessage",
12
+ "inputs": [
13
+ {
14
+ "name": "message",
15
+ "type": "tuple",
16
+ "components": [
17
+ { "name": "id", "type": "uint256" },
18
+ { "name": "sender", "type": "address" },
19
+ { "name": "target", "type": "address" },
20
+ { "name": "data", "type": "bytes" },
21
+ { "name": "sourceChain", "type": "uint32" }
22
+ ]
23
+ },
24
+ { "name": "proof", "type": "bytes" }
25
+ ],
26
+ "outputs": [{ "name": "success", "type": "bool" }],
27
+ "stateMutability": "nonpayable"
28
+ },
29
+ {
30
+ "type": "function",
31
+ "name": "proofPayload",
32
+ "inputs": [
33
+ {
34
+ "name": "message",
35
+ "type": "tuple",
36
+ "components": [
37
+ { "name": "id", "type": "uint256" },
38
+ { "name": "sender", "type": "address" },
39
+ { "name": "target", "type": "address" },
40
+ { "name": "data", "type": "bytes" },
41
+ { "name": "sourceChain", "type": "uint32" }
42
+ ]
43
+ }
44
+ ],
45
+ "outputs": [{ "name": "", "type": "bytes32" }],
46
+ "stateMutability": "pure"
47
+ },
48
+ {
49
+ "type": "function",
50
+ "name": "nonExecutionProofPayload",
51
+ "inputs": [
52
+ { "name": "messageId", "type": "uint256" },
53
+ { "name": "destinationChain", "type": "uint32" }
54
+ ],
55
+ "outputs": [{ "name": "", "type": "bytes32" }],
56
+ "stateMutability": "pure"
57
+ },
58
+ {
59
+ "type": "function",
60
+ "name": "setRelayer",
61
+ "inputs": [
62
+ { "name": "relayer", "type": "address" },
63
+ { "name": "authorized", "type": "bool" }
64
+ ],
65
+ "outputs": [],
66
+ "stateMutability": "nonpayable"
67
+ },
68
+ {
69
+ "type": "function",
70
+ "name": "setHubSigningKey",
71
+ "inputs": [{ "name": "newKey", "type": "address" }],
72
+ "outputs": [],
73
+ "stateMutability": "nonpayable"
74
+ },
75
+ {
76
+ "type": "function",
77
+ "name": "transferOwnership",
78
+ "inputs": [{ "name": "newOwner", "type": "address" }],
79
+ "outputs": [],
80
+ "stateMutability": "nonpayable"
81
+ },
82
+ {
83
+ "type": "function",
84
+ "name": "isProcessed",
85
+ "inputs": [
86
+ {
87
+ "name": "message",
88
+ "type": "tuple",
89
+ "components": [
90
+ { "name": "id", "type": "uint256" },
91
+ { "name": "sender", "type": "address" },
92
+ { "name": "target", "type": "address" },
93
+ { "name": "data", "type": "bytes" },
94
+ { "name": "sourceChain", "type": "uint32" }
95
+ ]
96
+ }
97
+ ],
98
+ "outputs": [{ "name": "", "type": "bool" }],
99
+ "stateMutability": "view"
100
+ },
101
+ {
102
+ "type": "function",
103
+ "name": "getReceipt",
104
+ "inputs": [
105
+ {
106
+ "name": "message",
107
+ "type": "tuple",
108
+ "components": [
109
+ { "name": "id", "type": "uint256" },
110
+ { "name": "sender", "type": "address" },
111
+ { "name": "target", "type": "address" },
112
+ { "name": "data", "type": "bytes" },
113
+ { "name": "sourceChain", "type": "uint32" }
114
+ ]
115
+ }
116
+ ],
117
+ "outputs": [
118
+ { "name": "executed", "type": "bool" },
119
+ { "name": "success", "type": "bool" },
120
+ { "name": "timestamp", "type": "uint256" }
121
+ ],
122
+ "stateMutability": "view"
123
+ },
124
+ {
125
+ "type": "function",
126
+ "name": "messageHub",
127
+ "inputs": [],
128
+ "outputs": [{ "name": "", "type": "address" }],
129
+ "stateMutability": "view"
130
+ },
131
+ {
132
+ "type": "function",
133
+ "name": "owner",
134
+ "inputs": [],
135
+ "outputs": [{ "name": "", "type": "address" }],
136
+ "stateMutability": "view"
137
+ },
138
+ {
139
+ "type": "function",
140
+ "name": "hubSigningKey",
141
+ "inputs": [],
142
+ "outputs": [{ "name": "", "type": "address" }],
143
+ "stateMutability": "view"
144
+ },
145
+ {
146
+ "type": "function",
147
+ "name": "authorizedRelayers",
148
+ "inputs": [{ "name": "", "type": "address" }],
149
+ "outputs": [{ "name": "", "type": "bool" }],
150
+ "stateMutability": "view"
151
+ },
152
+ {
153
+ "type": "function",
154
+ "name": "processedMessages",
155
+ "inputs": [{ "name": "", "type": "bytes32" }],
156
+ "outputs": [{ "name": "", "type": "bool" }],
157
+ "stateMutability": "view"
158
+ },
159
+ {
160
+ "type": "function",
161
+ "name": "totalExecuted",
162
+ "inputs": [],
163
+ "outputs": [{ "name": "", "type": "uint256" }],
164
+ "stateMutability": "view"
165
+ },
166
+ {
167
+ "type": "function",
168
+ "name": "totalFailed",
169
+ "inputs": [],
170
+ "outputs": [{ "name": "", "type": "uint256" }],
171
+ "stateMutability": "view"
172
+ },
173
+ {
174
+ "type": "event",
175
+ "name": "MessageReceived",
176
+ "inputs": [
177
+ { "name": "messageId", "type": "uint256", "indexed": true },
178
+ { "name": "sender", "type": "address", "indexed": true },
179
+ { "name": "target", "type": "address", "indexed": true },
180
+ { "name": "success", "type": "bool", "indexed": false }
181
+ ]
182
+ },
183
+ {
184
+ "type": "event",
185
+ "name": "RelayerAuthorized",
186
+ "inputs": [
187
+ { "name": "relayer", "type": "address", "indexed": true },
188
+ { "name": "authorized", "type": "bool", "indexed": false }
189
+ ]
190
+ },
191
+ {
192
+ "type": "event",
193
+ "name": "HubSigningKeyUpdated",
194
+ "inputs": [
195
+ { "name": "newKey", "type": "address", "indexed": true }
196
+ ]
197
+ },
198
+ {
199
+ "type": "event",
200
+ "name": "OwnershipTransferred",
201
+ "inputs": [
202
+ { "name": "previousOwner", "type": "address", "indexed": true },
203
+ { "name": "newOwner", "type": "address", "indexed": true }
204
+ ]
205
+ },
206
+ { "type": "error", "name": "NotRelayer", "inputs": [] },
207
+ { "type": "error", "name": "AlreadyProcessed", "inputs": [{ "name": "msgHash", "type": "bytes32" }] },
208
+ { "type": "error", "name": "InvalidSignature", "inputs": [] },
209
+ { "type": "error", "name": "InvalidTarget", "inputs": [] },
210
+ { "type": "error", "name": "Unauthorized", "inputs": [] }
211
+ ]
@@ -0,0 +1,60 @@
1
+ import type { ChainConfig, ChainName } from './types';
2
+
3
+ // ── Contract addresses ────────────────────────────────────────────────────────
4
+
5
+ /** MessageHub deployed on Arbitrum Sepolia (421614) */
6
+ export const MESSAGE_HUB_ADDRESS = '0x9a9e7Ec4EA29bb63fE7c38E124B253b44fF897Cc';
7
+
8
+ /** ArbiLinkReceiver addresses on each destination chain */
9
+ export const RECEIVER_ADDRESSES: Record<number, string> = {
10
+ 11155111: '0x895058E57bBE8c84C2AABA5d61c4C739C5869F71', // Ethereum Sepolia
11
+ 84532: '0xD45efE42904C9a27630A548A1FB6d9F133Cf5D35', // Base Sepolia
12
+ 80002: '0x221B7Cca1C385C6c81e17b086C753328AF41AAAa', // Polygon Amoy
13
+ };
14
+
15
+ // ── Supported chains ──────────────────────────────────────────────────────────
16
+
17
+ export const SUPPORTED_CHAINS: ChainConfig[] = [
18
+ {
19
+ id: 11155111,
20
+ name: 'Ethereum Sepolia',
21
+ shortName: 'ethereum',
22
+ rpc: 'https://sepolia.infura.io/v3/',
23
+ explorer: 'https://sepolia.etherscan.io',
24
+ receiverAddress: RECEIVER_ADDRESSES[11155111],
25
+ },
26
+ {
27
+ id: 84532,
28
+ name: 'Base Sepolia',
29
+ shortName: 'base',
30
+ rpc: 'https://sepolia.base.org',
31
+ explorer: 'https://sepolia.basescan.org',
32
+ receiverAddress: RECEIVER_ADDRESSES[84532],
33
+ },
34
+ {
35
+ id: 80002,
36
+ name: 'Polygon Amoy',
37
+ shortName: 'polygon',
38
+ rpc: 'https://rpc-amoy.polygon.technology',
39
+ explorer: 'https://amoy.polygonscan.com',
40
+ receiverAddress: RECEIVER_ADDRESSES[80002],
41
+ },
42
+ ];
43
+
44
+ /** Map chain short-names to numeric IDs */
45
+ export const CHAIN_IDS: Record<ChainName, number> = {
46
+ ethereum: 11155111, // Sepolia
47
+ base: 84532, // Base Sepolia
48
+ polygon: 80002, // Amoy testnet
49
+ optimism: 11155420, // Optimism Sepolia
50
+ };
51
+
52
+ // ── Challenge window ──────────────────────────────────────────────────────────
53
+
54
+ /** Default challenge period in seconds (matches hub deployment param) */
55
+ export const DEFAULT_CHALLENGE_PERIOD_SECS = 300; // 5 minutes
56
+
57
+ // ── Arbitrum Sepolia hub chain ────────────────────────────────────────────────
58
+
59
+ export const ARBITRUM_SEPOLIA_CHAIN_ID = 421614;
60
+ export const ARBITRUM_SEPOLIA_RPC = 'https://sepolia-rollup.arbitrum.io/rpc';
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Main SDK class
2
+ export { ArbiLink } from './ArbiLink';
3
+
4
+ // Types
5
+ export type {
6
+ ChainId,
7
+ ChainName,
8
+ ChainConfig,
9
+ Message,
10
+ MessageStatus,
11
+ RelayerInfo,
12
+ SendMessageParams,
13
+ WatchOptions,
14
+ } from './types';
15
+ export { ArbiLinkError } from './types';
16
+
17
+ // Utilities
18
+ export {
19
+ encodeCall,
20
+ formatMessageId,
21
+ formatEth,
22
+ statusLabel,
23
+ resolveChainId,
24
+ estimateDeliveryTime,
25
+ parseStatusCode,
26
+ } from './utils';
27
+
28
+ // Constants
29
+ export {
30
+ MESSAGE_HUB_ADDRESS,
31
+ RECEIVER_ADDRESSES,
32
+ SUPPORTED_CHAINS,
33
+ CHAIN_IDS,
34
+ ARBITRUM_SEPOLIA_CHAIN_ID,
35
+ ARBITRUM_SEPOLIA_RPC,
36
+ DEFAULT_CHALLENGE_PERIOD_SECS,
37
+ } from './constants';
38
+
39
+ // ABIs (useful for integrators building their own wrappers)
40
+ export { default as MessageHubABI } from './abi/MessageHub.json';
41
+ export { default as ReceiverABI } from './abi/Receiver.json';
package/src/types.ts ADDED
@@ -0,0 +1,82 @@
1
+ // ── Chain types ───────────────────────────────────────────────────────────────
2
+
3
+ export type ChainId = 11155111 | 84532 | 80002 | 11155420;
4
+
5
+ export type ChainName = 'ethereum' | 'base' | 'polygon' | 'optimism';
6
+
7
+ export interface ChainConfig {
8
+ id: number;
9
+ name: string;
10
+ shortName: ChainName;
11
+ rpc: string;
12
+ explorer: string;
13
+ receiverAddress: string;
14
+ }
15
+
16
+ // ── Message types ─────────────────────────────────────────────────────────────
17
+
18
+ /** On-chain status codes (matches STATUS_* constants in MessageHub) */
19
+ export type MessageStatus = 'pending' | 'relayed' | 'confirmed' | 'failed';
20
+
21
+ /** Full message record as returned by the SDK */
22
+ export interface Message {
23
+ id: bigint;
24
+ status: MessageStatus;
25
+ /** Sender address — populated from MessageSent event logs */
26
+ sender?: string;
27
+ /** Destination chain ID — populated from MessageSent event logs */
28
+ destinationChain?: number;
29
+ /** Target contract on the destination chain */
30
+ target?: string;
31
+ /** Encoded calldata for the target */
32
+ data?: string;
33
+ /** Protocol fee paid (in wei) — populated from MessageSent event logs */
34
+ feePaid?: bigint;
35
+ /** Relayer address, present once a relayer confirms delivery */
36
+ relayer?: string;
37
+ }
38
+
39
+ // ── SDK parameter types ───────────────────────────────────────────────────────
40
+
41
+ export interface SendMessageParams {
42
+ /** Destination chain – either a numeric chain ID or a supported chain name */
43
+ to: number | ChainName;
44
+ /** Target contract address on the destination chain */
45
+ target: string;
46
+ /** ABI-encoded function call (use encodeCall() from utils) */
47
+ data: string;
48
+ /** Override the auto-calculated fee (wei). Fetched from hub if omitted. */
49
+ fee?: bigint;
50
+ }
51
+
52
+ export interface WatchOptions {
53
+ /** Poll interval in ms when WebSocket is unavailable (default: 3000) */
54
+ pollIntervalMs?: number;
55
+ }
56
+
57
+ // ── Relayer types ─────────────────────────────────────────────────────────────
58
+
59
+ export interface RelayerInfo {
60
+ active: boolean;
61
+ stake: bigint;
62
+ }
63
+
64
+ // ── Error ─────────────────────────────────────────────────────────────────────
65
+
66
+ export class ArbiLinkError extends Error {
67
+ constructor(
68
+ message: string,
69
+ public readonly cause?: unknown,
70
+ ) {
71
+ super(message);
72
+ this.name = 'ArbiLinkError';
73
+ // Preserve prototype chain in compiled JS
74
+ Object.setPrototypeOf(this, ArbiLinkError.prototype);
75
+ }
76
+
77
+ static from(err: unknown, context: string): ArbiLinkError {
78
+ if (err instanceof ArbiLinkError) return err;
79
+ const detail = err instanceof Error ? err.message : String(err);
80
+ return new ArbiLinkError(`${context}: ${detail}`, err);
81
+ }
82
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,111 @@
1
+ import { encodeFunctionData, type Abi } from 'viem';
2
+ import type { MessageStatus } from './types';
3
+ import { CHAIN_IDS } from './constants';
4
+
5
+ // ── Encoding helpers ──────────────────────────────────────────────────────────
6
+
7
+ /**
8
+ * Encode a contract function call into hex calldata ready for cross-chain delivery.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const data = encodeCall({
13
+ * abi: parseAbi(['function mint(address to, uint256 amount)']),
14
+ * functionName: 'mint',
15
+ * args: ['0x742d35Cc...', 1000n],
16
+ * });
17
+ * ```
18
+ */
19
+ export function encodeCall(params: {
20
+ abi: Abi;
21
+ functionName: string;
22
+ args?: readonly unknown[];
23
+ }): string {
24
+ return encodeFunctionData({
25
+ abi: params.abi,
26
+ functionName: params.functionName,
27
+ args: params.args ?? [],
28
+ }) as string;
29
+ }
30
+
31
+ // ── Formatting ────────────────────────────────────────────────────────────────
32
+
33
+ /**
34
+ * Format a numeric message ID as a zero-padded string.
35
+ * @example formatMessageId(42n) → "#000042"
36
+ */
37
+ export function formatMessageId(id: bigint): string {
38
+ return `#${id.toString().padStart(6, '0')}`;
39
+ }
40
+
41
+ /**
42
+ * Human-readable label for a message status.
43
+ */
44
+ export function statusLabel(status: MessageStatus): string {
45
+ const labels: Record<MessageStatus, string> = {
46
+ pending: 'Pending',
47
+ relayed: 'Relayed (in challenge window)',
48
+ confirmed: 'Confirmed',
49
+ failed: 'Failed',
50
+ };
51
+ return labels[status];
52
+ }
53
+
54
+ // ── Chain helpers ─────────────────────────────────────────────────────────────
55
+
56
+ /**
57
+ * Resolve a chain name or numeric ID to a numeric chain ID.
58
+ * Throws `ArbiLinkError` for unknown chains.
59
+ */
60
+ export function resolveChainId(chain: number | string): number {
61
+ if (typeof chain === 'number') return chain;
62
+
63
+ const id = CHAIN_IDS[chain.toLowerCase() as keyof typeof CHAIN_IDS];
64
+ if (id == null) {
65
+ throw new Error(
66
+ `Unknown chain "${chain}". Supported names: ${Object.keys(CHAIN_IDS).join(', ')}`,
67
+ );
68
+ }
69
+ return id;
70
+ }
71
+
72
+ /**
73
+ * Estimate message delivery time in seconds for a given destination chain.
74
+ *
75
+ * This is a rough heuristic based on block times – actual delivery depends on
76
+ * relayer availability and the 5-minute challenge window.
77
+ */
78
+ export function estimateDeliveryTime(chainId: number): number {
79
+ const CHALLENGE_WINDOW = 300; // 5 min in seconds
80
+
81
+ const FINALITY_TIMES: Record<number, number> = {
82
+ 11155111: 15, // Ethereum Sepolia
83
+ 84532: 2, // Base Sepolia
84
+ 80002: 2, // Polygon Amoy
85
+ 11155420: 2, // Optimism Sepolia
86
+ };
87
+
88
+ return CHALLENGE_WINDOW + (FINALITY_TIMES[chainId] ?? 12);
89
+ }
90
+
91
+ // ── Status helpers ────────────────────────────────────────────────────────────
92
+
93
+ const STATUS_MAP: MessageStatus[] = ['pending', 'relayed', 'confirmed', 'failed'];
94
+
95
+ /**
96
+ * Convert the on-chain numeric status code to the SDK string representation.
97
+ */
98
+ export function parseStatusCode(code: number): MessageStatus {
99
+ return STATUS_MAP[code] ?? 'pending';
100
+ }
101
+
102
+ // ── Wei helpers ───────────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Format a wei value as a human-readable ETH string.
106
+ * @example formatEth(1_000_000_000_000_000n) → "0.001 ETH"
107
+ */
108
+ export function formatEth(wei: bigint): string {
109
+ const eth = Number(wei) / 1e18;
110
+ return `${eth.toFixed(6).replace(/\.?0+$/, '')} ETH`;
111
+ }