@mayanfinance/blink-sdk 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,3 +13,16 @@ const { blink } = createBlinkClient({ apiKey: process.env.BLINK_API_KEY });
13
13
  ```
14
14
 
15
15
  The client talks to `https://api.hular.dev` unless `baseUrl` is passed. Full documentation is at https://docs.hular.dev.
16
+
17
+ ## Contract registry
18
+
19
+ Every contract address a deposit touches is checked against a signed manifest served by `GetContracts`. The SDK pins the genesis guardian set, verifies each later set was approved by a majority of the previous one, and accepts the manifest only when every member of the latest set has signed it. `client.contracts` caches the manifest for two hours, refetches when a quote references a newer guardian set or an address outside the cache, and throws `UnregisteredAddressError` if the address is still unknown.
20
+
21
+ Guardians sign with `scripts/sign-contracts.mjs`:
22
+
23
+ ```
24
+ CONTRACTS_SIGNER_KEY=0x... node scripts/sign-contracts.mjs manifest ../config/contracts.json
25
+ CONTRACTS_SIGNER_KEY=0x... node scripts/sign-contracts.mjs guardians ../config/guardians.json 1
26
+ ```
27
+
28
+ The first form writes the signature next to the manifest in `contracts.sig.json`; the second approves the guardian set with that index on behalf of a member of the previous set.
package/dist/client.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type Client } from "@connectrpc/connect";
2
+ import { type ContractRegistry } from "./contracts.js";
2
3
  import { BlinkApi } from "./gen/blink/v1/api_pb.js";
3
4
  import { AdminApi } from "./gen/blink/v1/admin_pb.js";
4
5
  import { PartnerApi } from "./gen/blink/v1/partner_pb.js";
@@ -13,5 +14,6 @@ export interface BlinkClient {
13
14
  blink: Client<typeof BlinkApi>;
14
15
  partner: Client<typeof PartnerApi>;
15
16
  admin: Client<typeof AdminApi>;
17
+ contracts: ContractRegistry;
16
18
  }
17
19
  export declare const createBlinkClient: ({ baseUrl, apiKey, token, telemetry, }: BlinkClientOptions) => BlinkClient;
package/dist/client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createClient } from "@connectrpc/connect";
2
2
  import { createGrpcWebTransport } from "@connectrpc/connect-web";
3
+ import { ApiContractRegistry } from "./contracts.js";
3
4
  import { BlinkApi } from "./gen/blink/v1/api_pb.js";
4
5
  import { AdminApi } from "./gen/blink/v1/admin_pb.js";
5
6
  import { PartnerApi } from "./gen/blink/v1/partner_pb.js";
@@ -24,9 +25,11 @@ export const createBlinkClient = ({ baseUrl = BLINK_API_URL, apiKey, token, tele
24
25
  baseUrl,
25
26
  interceptors: [headers],
26
27
  });
28
+ const blink = createClient(BlinkApi, transport);
27
29
  return {
28
- blink: createClient(BlinkApi, transport),
30
+ blink,
29
31
  partner: createClient(PartnerApi, transport),
30
32
  admin: createClient(AdminApi, transport),
33
+ contracts: new ApiContractRegistry(blink),
31
34
  };
32
35
  };
@@ -0,0 +1,40 @@
1
+ import type { Client } from "@connectrpc/connect";
2
+ import type { BlinkApi, ContractsResponse, GuardianSet } from "./gen/blink/v1/api_pb.js";
3
+ export declare const GENESIS_GUARDIANS: readonly string[];
4
+ export declare const CONTRACTS_REFRESH_MS: number;
5
+ export interface ChainContracts {
6
+ router: string;
7
+ escrow?: string;
8
+ swapPrograms: string[];
9
+ }
10
+ export interface ContractManifest {
11
+ guardianSetIndex: number;
12
+ chains: Record<string, ChainContracts>;
13
+ }
14
+ export declare class UnregisteredAddressError extends Error {
15
+ readonly chain: string;
16
+ readonly address: string;
17
+ constructor(chain: string, address: string);
18
+ }
19
+ export interface ContractRegistry {
20
+ contracts(chain: string, guardianSetIndex?: number): Promise<ChainContracts>;
21
+ assertRegistered(chain: string, address: string, guardianSetIndex?: number): Promise<void>;
22
+ }
23
+ export declare const sameAddress: (a: string, b: string) => boolean;
24
+ export declare const guardianMessage: (index: number, members: string[]) => string;
25
+ export declare const verifyGuardianSets: (sets: GuardianSet[], genesis?: readonly string[]) => Promise<GuardianSet>;
26
+ export declare const verifyManifestSignatures: (manifest: string, signatures: string[], members: string[]) => Promise<void>;
27
+ export declare const verifyContracts: (response: ContractsResponse, genesis?: readonly string[]) => Promise<ContractManifest>;
28
+ export declare class ApiContractRegistry implements ContractRegistry {
29
+ private api;
30
+ private cached?;
31
+ private inflight?;
32
+ constructor(api: Client<typeof BlinkApi>);
33
+ contracts(chain: string, guardianSetIndex?: number): Promise<ChainContracts>;
34
+ assertRegistered(chain: string, address: string, guardianSetIndex?: number): Promise<void>;
35
+ private lookup;
36
+ private manifest;
37
+ private current;
38
+ private refresh;
39
+ private fetch;
40
+ }
@@ -0,0 +1,160 @@
1
+ import { recoverMessageAddress } from "viem";
2
+ export const GENESIS_GUARDIANS = [
3
+ "0x4FbA3CE3CC1eb4297E274f42B1C252Ba42F886a6",
4
+ "0x024721C2d41739BCdE70EEc65e73E2B27726dDA9",
5
+ "0x9E497d060D1543Bd360db003Da7a13924a2dB8cb",
6
+ ];
7
+ export const CONTRACTS_REFRESH_MS = 2 * 60 * 60 * 1000;
8
+ export class UnregisteredAddressError extends Error {
9
+ chain;
10
+ address;
11
+ constructor(chain, address) {
12
+ super(`${address} is not a registered contract on ${chain}`);
13
+ this.chain = chain;
14
+ this.address = address;
15
+ this.name = "UnregisteredAddressError";
16
+ }
17
+ }
18
+ export const sameAddress = (a, b) => a.startsWith("0x") ? a.toLowerCase() === b.toLowerCase() : a === b;
19
+ export const guardianMessage = (index, members) => `blink-guardians:${index}:${members.map((m) => m.toLowerCase()).join(",")}`;
20
+ const recoveredSigners = async (message, signatures) => {
21
+ const signers = new Set();
22
+ for (const signature of signatures) {
23
+ const signer = await recoverMessageAddress({
24
+ message,
25
+ signature: signature,
26
+ });
27
+ signers.add(signer.toLowerCase());
28
+ }
29
+ return signers;
30
+ };
31
+ const sameMembers = (a, b) => a.length === b.length && a.every((m, i) => sameAddress(m, b[i]));
32
+ export const verifyGuardianSets = async (sets, genesis = GENESIS_GUARDIANS) => {
33
+ const first = sets[0];
34
+ if (!first || first.index !== 0 || !sameMembers(first.members, genesis)) {
35
+ throw new Error("guardian set 0 does not match the pinned genesis set");
36
+ }
37
+ for (let i = 1; i < sets.length; i++) {
38
+ const previous = sets[i - 1];
39
+ const set = sets[i];
40
+ if (set.index !== i) {
41
+ throw new Error(`guardian set ${set.index} is out of sequence`);
42
+ }
43
+ if (set.members.length === 0 ||
44
+ new Set(set.members.map((m) => m.toLowerCase())).size !==
45
+ set.members.length) {
46
+ throw new Error(`guardian set ${i} members must be non-empty and distinct`);
47
+ }
48
+ const signers = await recoveredSigners(guardianMessage(i, set.members), set.signatures);
49
+ const approvals = previous.members.filter((m) => signers.has(m.toLowerCase())).length;
50
+ const quorum = Math.floor(previous.members.length / 2) + 1;
51
+ if (approvals < quorum) {
52
+ throw new Error(`guardian set ${i} has ${approvals} of ${previous.members.length} approvals, quorum is ${quorum}`);
53
+ }
54
+ }
55
+ return sets[sets.length - 1];
56
+ };
57
+ export const verifyManifestSignatures = async (manifest, signatures, members) => {
58
+ const signers = await recoveredSigners({ raw: new TextEncoder().encode(manifest) }, signatures);
59
+ const missing = members.find((m) => !signers.has(m.toLowerCase()));
60
+ if (missing) {
61
+ throw new Error(`guardian ${missing} has not signed the contract manifest`);
62
+ }
63
+ };
64
+ const isStringList = (value) => Array.isArray(value) && value.every((v) => typeof v === "string");
65
+ const parseManifest = (raw) => {
66
+ const parsed = JSON.parse(raw);
67
+ if (typeof parsed !== "object" ||
68
+ parsed === null ||
69
+ typeof parsed.guardian_set_index !==
70
+ "number" ||
71
+ typeof parsed.chains !== "object") {
72
+ throw new Error("contract manifest is malformed");
73
+ }
74
+ const { guardian_set_index, chains } = parsed;
75
+ const out = {};
76
+ for (const [chain, entry] of Object.entries(chains)) {
77
+ const { router, escrow, swap_programs } = entry;
78
+ if (typeof router !== "string" ||
79
+ (escrow !== undefined && typeof escrow !== "string") ||
80
+ (swap_programs !== undefined && !isStringList(swap_programs))) {
81
+ throw new Error(`contract manifest entry for ${chain} is malformed`);
82
+ }
83
+ out[chain] = { router, escrow, swapPrograms: swap_programs ?? [] };
84
+ }
85
+ return { guardianSetIndex: guardian_set_index, chains: out };
86
+ };
87
+ export const verifyContracts = async (response, genesis = GENESIS_GUARDIANS) => {
88
+ const latest = await verifyGuardianSets(response.guardianSets, genesis);
89
+ await verifyManifestSignatures(response.manifest, response.signatures, latest.members);
90
+ const manifest = parseManifest(response.manifest);
91
+ if (manifest.guardianSetIndex !== latest.index) {
92
+ throw new Error(`contract manifest is signed under guardian set ${manifest.guardianSetIndex}, latest is ${latest.index}`);
93
+ }
94
+ return manifest;
95
+ };
96
+ export class ApiContractRegistry {
97
+ api;
98
+ cached;
99
+ inflight;
100
+ constructor(api) {
101
+ this.api = api;
102
+ }
103
+ async contracts(chain, guardianSetIndex) {
104
+ return this.lookup(guardianSetIndex, (manifest) => manifest.chains[chain], () => new UnregisteredAddressError(chain, "every contract"));
105
+ }
106
+ async assertRegistered(chain, address, guardianSetIndex) {
107
+ await this.lookup(guardianSetIndex, (manifest) => {
108
+ const contracts = manifest.chains[chain];
109
+ return [contracts?.router, contracts?.escrow].find((registered) => registered !== undefined && sameAddress(registered, address));
110
+ }, () => new UnregisteredAddressError(chain, address));
111
+ }
112
+ async lookup(guardianSetIndex, pick, missing) {
113
+ const first = pick(await this.manifest(guardianSetIndex));
114
+ if (first !== undefined) {
115
+ return first;
116
+ }
117
+ await this.refresh();
118
+ const second = pick(await this.manifest(guardianSetIndex));
119
+ if (second !== undefined) {
120
+ return second;
121
+ }
122
+ throw missing();
123
+ }
124
+ async manifest(guardianSetIndex) {
125
+ let current = await this.current();
126
+ if (guardianSetIndex !== undefined &&
127
+ guardianSetIndex > current.guardianSetIndex) {
128
+ current = await this.refresh();
129
+ }
130
+ if (guardianSetIndex !== undefined &&
131
+ guardianSetIndex !== current.guardianSetIndex) {
132
+ throw new Error(`quote was issued under guardian set ${guardianSetIndex}, latest is ${current.guardianSetIndex}`);
133
+ }
134
+ return current;
135
+ }
136
+ current() {
137
+ if (!this.cached ||
138
+ Date.now() - this.cached.fetchedAt > CONTRACTS_REFRESH_MS) {
139
+ return this.refresh();
140
+ }
141
+ return Promise.resolve(this.cached.manifest);
142
+ }
143
+ refresh() {
144
+ if (!this.inflight) {
145
+ this.inflight = this.fetch().finally(() => {
146
+ this.inflight = undefined;
147
+ });
148
+ }
149
+ return this.inflight;
150
+ }
151
+ async fetch() {
152
+ const manifest = await verifyContracts(await this.api.getContracts({}));
153
+ if (this.cached &&
154
+ manifest.guardianSetIndex < this.cached.manifest.guardianSetIndex) {
155
+ throw new Error(`contract manifest rolled back to guardian set ${manifest.guardianSetIndex}`);
156
+ }
157
+ this.cached = { manifest, fetchedAt: Date.now() };
158
+ return manifest;
159
+ }
160
+ }
package/dist/deposit.d.ts CHANGED
@@ -2,17 +2,19 @@ import type { BlinkClient } from "./client.js";
2
2
  import type { ChainStatus, OrderParams } from "./gen/blink/v1/api_pb.js";
3
3
  import { type ConnectedWallet, type WalletKind } from "./wallets.js";
4
4
  export interface DepositStep {
5
- kind: "approve" | "deposit";
5
+ kind: "approve" | "deposit" | "swap";
6
6
  fee?: bigint;
7
7
  run: () => Promise<string>;
8
8
  }
9
9
  export interface GaslessQuote {
10
+ guardianSetIndex?: number;
10
11
  sponsorAddress?: string;
11
12
  sponsorGasBudget?: bigint;
12
13
  depositDeadline?: string;
13
14
  }
14
15
  export type SvmComputeUnitPrice = "auto" | bigint;
15
16
  export interface DepositOptions {
17
+ guardianSetIndex?: number;
16
18
  directTransfer?: boolean;
17
19
  depositDeadline?: string;
18
20
  svmComputeUnitPrice?: SvmComputeUnitPrice;
@@ -43,20 +45,20 @@ export declare class EvmChainAdapter implements ChainAdapter {
43
45
  gaslessSteps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, quote: GaslessQuote): Promise<DepositStep[]>;
44
46
  }
45
47
  export declare class SvmChainAdapter implements ChainAdapter {
48
+ private client;
46
49
  private chain;
47
- private client?;
48
50
  readonly kind = "svm";
49
- constructor(chain: ChainStatus, client?: BlinkClient | undefined);
51
+ constructor(client: BlinkClient, chain: ChainStatus);
50
52
  balance(owner: string, token: string): Promise<bigint>;
51
53
  canPayGas(owner: string): Promise<boolean>;
52
54
  steps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, options?: DepositOptions): Promise<DepositStep[]>;
53
55
  gaslessSteps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, quote: GaslessQuote): Promise<DepositStep[]>;
54
56
  }
55
57
  export declare class SuiChainAdapter implements ChainAdapter {
58
+ private client;
56
59
  private chain;
57
- private client?;
58
60
  readonly kind = "sui";
59
- constructor(chain: ChainStatus, client?: BlinkClient | undefined);
61
+ constructor(client: BlinkClient, chain: ChainStatus);
60
62
  balance(owner: string, token: string): Promise<bigint>;
61
63
  canPayGas(owner: string): Promise<boolean>;
62
64
  steps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, options?: DepositOptions): Promise<DepositStep[]>;
package/dist/deposit.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { Code, ConnectError } from "@connectrpc/connect";
1
2
  import { formatAmount } from "./amount.js";
2
3
  import { buildEvmDepositCall, detectPermit, encodeApprove, evmAllowance, evmAllowanceSlot, evmEstimateGas, evmGasPrice, permit2WitnessDepositTypedData, permit2WitnessSwapDepositTypedData, evmBalance, isNativeEvmToken, PERMIT2_ADDRESS, permit2TypedData, permitDeadline, permitTypedData, randomPermit2Nonce, splitSignature, usesDirectTransfer, waitForEvmTransaction, } from "./evm.js";
3
4
  import { assertQuoteHash } from "./hash.js";
4
5
  import { assertDepositDeadline, isSourceSwap } from "./order.js";
5
- import { SUI_COIN_TYPE, buildSuiDeposit, buildSuiSwapDeposit, sponsorSuiTransaction, suiBalance, } from "./sui.js";
6
+ import { SUI_COIN_TYPE, buildSuiDeposit, buildSuiSourceSwapOpen, sponsorSuiTransaction, suiBalance, } from "./sui.js";
6
7
  import { buildSvmDeposit, buildSvmSwapDeposit, svmBalance } from "./svm.js";
7
8
  import { sendEvmCalls, sendEvmTransaction, signAndSendSuiTransaction, signAndSendSvmTransaction, signSuiTransaction, signSvmTransaction, signEvmTypedData, supportsAtomicBatch, switchEvmChain, } from "./wallets.js";
8
9
  const trackGaslessSubmission = async (client, quoteHash) => {
@@ -17,6 +18,43 @@ const trackGaslessSubmission = async (client, quoteHash) => {
17
18
  await new Promise((resolve) => setTimeout(resolve, 2000));
18
19
  }
19
20
  };
21
+ const SOURCE_SWAP_POLL_MS = 2000;
22
+ const SOURCE_SWAP_SETTLE_MS = 120_000;
23
+ const SOURCE_SWAP_UNSEEN_MS = 420_000;
24
+ const unlessNotFound = async (call) => {
25
+ try {
26
+ return await call();
27
+ }
28
+ catch (e) {
29
+ if (e instanceof ConnectError && e.code === Code.NotFound) {
30
+ return undefined;
31
+ }
32
+ throw e;
33
+ }
34
+ };
35
+ const trackSourceSwap = async (client, quoteHash) => {
36
+ const started = Date.now();
37
+ for (;;) {
38
+ const swap = await unlessNotFound(() => client.blink.getSourceSwap({ quoteHash }));
39
+ if (swap?.status === "executed") {
40
+ return swap.swapTx ?? swap.openTx;
41
+ }
42
+ const overdue = swap
43
+ ? Date.parse(swap.expiresAt) + SOURCE_SWAP_SETTLE_MS < Date.now()
44
+ : Date.now() - started > SOURCE_SWAP_UNSEEN_MS;
45
+ if (swap?.status === "lapsed" || overdue) {
46
+ const detail = await unlessNotFound(() => client.blink.getOrder({ quoteHash }));
47
+ if (detail?.order?.srcTx) {
48
+ return detail.order.srcTx;
49
+ }
50
+ throw new Error(`the source swap did not run (${swap?.reason ?? "expired"}); your funds never left your wallet`);
51
+ }
52
+ await new Promise((resolve) => setTimeout(resolve, SOURCE_SWAP_POLL_MS));
53
+ }
54
+ };
55
+ const sourceSwapStep = (client, quoteHash, params) => isSourceSwap(params)
56
+ ? [{ kind: "swap", run: () => trackSourceSwap(client, quoteHash) }]
57
+ : [];
20
58
  const rpcUrlOf = (chain) => {
21
59
  if (!chain.rpcUrl) {
22
60
  throw new Error(`no public rpc configured for ${chain.chain}`);
@@ -27,9 +65,6 @@ const svmComputeUnitPriceMicroLamports = async (client, chain, price) => {
27
65
  if (price !== "auto") {
28
66
  return price;
29
67
  }
30
- if (!client) {
31
- throw new Error("auto svm priority fees need an api client");
32
- }
33
68
  const fee = await client.blink.getPriorityFee({ chain });
34
69
  return fee.computeUnitPriceMicroLamports;
35
70
  };
@@ -102,6 +137,9 @@ export class EvmChainAdapter {
102
137
  ...call,
103
138
  });
104
139
  };
140
+ const registry = this.client.contracts;
141
+ const guardianSetIndex = options?.guardianSetIndex;
142
+ await registry.assertRegistered(params.srcChain, params.routerAddress, guardianSetIndex);
105
143
  const base = {
106
144
  quoteHash,
107
145
  params,
@@ -113,16 +151,17 @@ export class EvmChainAdapter {
113
151
  await this.assertCanPay(wallet.address, fee);
114
152
  return [{ kind: "deposit", fee, run: () => send(call) }];
115
153
  }
116
- const request = {
117
- ...base,
118
- swap: isSourceSwap(params)
119
- ? await this.client.blink.getSwapInstructions({ quoteHash })
120
- : undefined,
121
- router: this.chain.router,
122
- };
154
+ const swap = isSourceSwap(params)
155
+ ? await this.client.blink.getSwapInstructions({ quoteHash })
156
+ : undefined;
157
+ if (swap) {
158
+ await registry.assertRegistered(params.srcChain, swap.routerAddress, guardianSetIndex);
159
+ }
160
+ const { router } = await registry.contracts(params.srcChain, guardianSetIndex);
161
+ const request = { ...base, swap, router };
123
162
  const call = buildEvmDepositCall(request);
124
163
  const target = call.to;
125
- const permitTarget = request.swap ? target : this.chain.router;
164
+ const permitTarget = swap ? target : router;
126
165
  const deposit = { kind: "deposit", run: () => send(call) };
127
166
  if (native) {
128
167
  deposit.fee = await feeOf(call);
@@ -222,10 +261,7 @@ export class EvmChainAdapter {
222
261
  }
223
262
  assertQuoteHash(quoteHash, params);
224
263
  assertDepositDeadline(quote.depositDeadline);
225
- const spender = this.chain.router;
226
- if (!spender) {
227
- throw new Error(`no router configured for ${this.chain.chain}`);
228
- }
264
+ const { router: spender } = await this.client.contracts.contracts(params.srcChain, quote.guardianSetIndex);
229
265
  const rpcUrl = rpcUrlOf(this.chain);
230
266
  const amountIn = BigInt(params.amountIn);
231
267
  if (isNativeEvmToken(params.srcToken)) {
@@ -292,12 +328,12 @@ export class EvmChainAdapter {
292
328
  }
293
329
  }
294
330
  export class SvmChainAdapter {
295
- chain;
296
331
  client;
332
+ chain;
297
333
  kind = "svm";
298
- constructor(chain, client) {
299
- this.chain = chain;
334
+ constructor(client, chain) {
300
335
  this.client = client;
336
+ this.chain = chain;
301
337
  }
302
338
  balance(owner, token) {
303
339
  return svmBalance(rpcUrlOf(this.chain), token, owner);
@@ -312,16 +348,12 @@ export class SvmChainAdapter {
312
348
  }
313
349
  assertQuoteHash(quoteHash, params);
314
350
  assertDepositDeadline(options?.depositDeadline);
315
- if (!this.chain.treasury) {
316
- throw new Error("svm deposits are unavailable");
317
- }
351
+ const client = this.client;
352
+ const registry = client.contracts;
353
+ const guardianSetIndex = options?.guardianSetIndex;
318
354
  const rpcUrl = rpcUrlOf(this.chain);
319
355
  const computeUnitPrice = options?.svmComputeUnitPrice ?? "auto";
320
356
  if (isSourceSwap(params)) {
321
- const client = this.client;
322
- if (!client) {
323
- throw new Error("svm swap deposits need an api client");
324
- }
325
357
  return [
326
358
  {
327
359
  kind: "deposit",
@@ -332,9 +364,10 @@ export class SvmChainAdapter {
332
364
  if (instructions.payload.case !== "svm") {
333
365
  throw new Error("expected svm swap instructions");
334
366
  }
367
+ await registry.assertRegistered(params.srcChain, instructions.routerAddress, guardianSetIndex);
368
+ const { swapPrograms } = await registry.contracts(params.srcChain, guardianSetIndex);
335
369
  const transaction = await buildSvmSwapDeposit({
336
370
  rpcUrl,
337
- chain: this.chain.chain,
338
371
  programId: instructions.routerAddress,
339
372
  owner: wallet.address,
340
373
  srcToken: params.srcToken,
@@ -344,7 +377,7 @@ export class SvmChainAdapter {
344
377
  quoteHash,
345
378
  swap: instructions.payload.value,
346
379
  computeUnitPriceMicroLamports: await svmComputeUnitPriceMicroLamports(client, this.chain.chain, computeUnitPrice),
347
- swapProgramSigner: this.chain.treasury,
380
+ swapPrograms,
348
381
  });
349
382
  return signAndSendSvmTransaction(wallet.wallet, wallet.address, transaction);
350
383
  },
@@ -355,6 +388,7 @@ export class SvmChainAdapter {
355
388
  {
356
389
  kind: "deposit",
357
390
  run: async () => {
391
+ await registry.assertRegistered(params.srcChain, params.routerAddress, guardianSetIndex);
358
392
  const transaction = await buildSvmDeposit({
359
393
  rpcUrl,
360
394
  programId: params.routerAddress,
@@ -362,7 +396,7 @@ export class SvmChainAdapter {
362
396
  token: params.srcToken,
363
397
  amount: BigInt(params.amountIn),
364
398
  quoteHash,
365
- computeUnitPriceMicroLamports: await svmComputeUnitPriceMicroLamports(this.client, this.chain.chain, computeUnitPrice),
399
+ computeUnitPriceMicroLamports: await svmComputeUnitPriceMicroLamports(client, this.chain.chain, computeUnitPrice),
366
400
  });
367
401
  return signAndSendSvmTransaction(wallet.wallet, wallet.address, transaction);
368
402
  },
@@ -385,15 +419,13 @@ export class SvmChainAdapter {
385
419
  if (!sponsor) {
386
420
  throw new Error("gasless deposits are unavailable on svm");
387
421
  }
388
- if (!this.client) {
389
- throw new Error("gasless deposits need an api client");
390
- }
391
422
  const client = this.client;
392
423
  const rpcUrl = rpcUrlOf(this.chain);
393
424
  return [
394
425
  {
395
426
  kind: "deposit",
396
427
  run: async () => {
428
+ await client.contracts.assertRegistered(params.srcChain, params.routerAddress, quote.guardianSetIndex);
397
429
  const transaction = await buildSvmDeposit({
398
430
  rpcUrl,
399
431
  programId: params.routerAddress,
@@ -419,12 +451,12 @@ export class SvmChainAdapter {
419
451
  }
420
452
  }
421
453
  export class SuiChainAdapter {
422
- chain;
423
454
  client;
455
+ chain;
424
456
  kind = "sui";
425
- constructor(chain, client) {
426
- this.chain = chain;
457
+ constructor(client, chain) {
427
458
  this.client = client;
459
+ this.chain = chain;
428
460
  }
429
461
  balance(owner, token) {
430
462
  return suiBalance(rpcUrlOf(this.chain), token, owner);
@@ -439,20 +471,17 @@ export class SuiChainAdapter {
439
471
  }
440
472
  assertQuoteHash(quoteHash, params);
441
473
  assertDepositDeadline(options?.depositDeadline);
442
- const escrow = this.chain.escrow;
443
- if (!escrow) {
444
- throw new Error("sui deposits are unavailable");
445
- }
446
- if (isSourceSwap(params) && !this.client) {
447
- throw new Error("sui swap deposits need an api client");
448
- }
449
474
  const client = this.client;
450
- const rpcUrl = rpcUrlOf(this.chain);
475
+ const chain = this.chain;
451
476
  return [
452
477
  {
453
478
  kind: "deposit",
454
- run: async () => signAndSendSuiTransaction(wallet.wallet, wallet.address, await suiDepositTransaction(client, rpcUrl, escrow, wallet.address, quoteHash, params)),
479
+ run: async () => signAndSendSuiTransaction(wallet.wallet, wallet.address, await suiDepositTransaction(client, chain, wallet.address, quoteHash, params, {
480
+ guardianSetIndex: options?.guardianSetIndex,
481
+ gasless: false,
482
+ })),
455
483
  },
484
+ ...sourceSwapStep(client, quoteHash, params),
456
485
  ];
457
486
  }
458
487
  async gaslessSteps(wallet, quoteHash, params, quote) {
@@ -461,25 +490,22 @@ export class SuiChainAdapter {
461
490
  }
462
491
  assertQuoteHash(quoteHash, params);
463
492
  assertDepositDeadline(quote.depositDeadline);
464
- const escrow = this.chain.escrow;
465
- if (!escrow) {
466
- throw new Error("sui deposits are unavailable");
467
- }
468
493
  const sponsor = quote.sponsorAddress;
469
494
  const gasBudget = quote.sponsorGasBudget;
470
495
  if (!sponsor || gasBudget === undefined) {
471
496
  throw new Error("gasless deposits are unavailable on sui");
472
497
  }
473
- if (!this.client) {
474
- throw new Error("gasless deposits need an api client");
475
- }
476
498
  const client = this.client;
477
- const rpcUrl = rpcUrlOf(this.chain);
499
+ const chain = this.chain;
500
+ const rpcUrl = rpcUrlOf(chain);
478
501
  return [
479
502
  {
480
503
  kind: "deposit",
481
504
  run: async () => {
482
- const transaction = await sponsorSuiTransaction(rpcUrl, await suiDepositTransaction(client, rpcUrl, escrow, wallet.address, quoteHash, params), sponsor, gasBudget);
505
+ const transaction = await sponsorSuiTransaction(rpcUrl, await suiDepositTransaction(client, chain, wallet.address, quoteHash, params, {
506
+ guardianSetIndex: quote.guardianSetIndex,
507
+ gasless: true,
508
+ }), sponsor, gasBudget);
483
509
  const signed = await signSuiTransaction(wallet.wallet, wallet.address, transaction);
484
510
  await client.blink.submitGaslessOrder({
485
511
  quoteHash,
@@ -491,10 +517,18 @@ export class SuiChainAdapter {
491
517
  return trackGaslessSubmission(client, quoteHash);
492
518
  },
493
519
  },
520
+ ...sourceSwapStep(client, quoteHash, params),
494
521
  ];
495
522
  }
496
523
  }
497
- const suiDepositTransaction = async (client, rpcUrl, escrow, owner, quoteHash, params) => {
524
+ const suiDepositTransaction = async (client, chain, owner, quoteHash, params, { guardianSetIndex, gasless }) => {
525
+ const rpcUrl = rpcUrlOf(chain);
526
+ const registry = client.contracts;
527
+ await registry.assertRegistered(params.srcChain, params.routerAddress, guardianSetIndex);
528
+ const { escrow } = await registry.contracts(params.srcChain, guardianSetIndex);
529
+ if (!escrow) {
530
+ throw new Error("sui deposits are unavailable");
531
+ }
498
532
  if (!isSourceSwap(params)) {
499
533
  return buildSuiDeposit({
500
534
  rpcUrl,
@@ -506,32 +540,29 @@ const suiDepositTransaction = async (client, rpcUrl, escrow, owner, quoteHash, p
506
540
  quoteHash,
507
541
  });
508
542
  }
509
- if (!client) {
510
- throw new Error("sui swap deposits need an api client");
511
- }
512
- const instructions = await client.blink.getSwapInstructions({ quoteHash });
513
- if (instructions.payload.case !== "sui") {
514
- throw new Error("expected sui swap instructions");
543
+ if (!chain.swapExecutor) {
544
+ throw new Error("sui source swaps are unavailable");
515
545
  }
516
- return buildSuiSwapDeposit({
546
+ return buildSuiSourceSwapOpen({
517
547
  rpcUrl,
518
548
  packageId: params.routerAddress,
519
549
  escrowObject: escrow,
550
+ swapExecutor: chain.swapExecutor,
520
551
  owner,
521
552
  srcToken: params.srcToken,
522
553
  bridgeToken: params.bridgeTokenSrc,
523
554
  amountIn: BigInt(params.amountIn),
524
555
  minBridgeOut: BigInt(params.minBridgeOut),
525
556
  quoteHash,
526
- swap: instructions.payload.value,
557
+ gasless,
527
558
  });
528
559
  };
529
560
  export const createChainAdapter = (client, chain, chainId) => {
530
561
  if (chain.vm === "svm") {
531
- return new SvmChainAdapter(chain, client);
562
+ return new SvmChainAdapter(client, chain);
532
563
  }
533
564
  if (chain.vm === "sui") {
534
- return new SuiChainAdapter(chain, client);
565
+ return new SuiChainAdapter(client, chain);
535
566
  }
536
567
  if (chainId === undefined) {
537
568
  throw new Error(`unknown chain id for ${chain.chain}`);