@mayanfinance/blink-sdk 0.1.8 → 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,18 +2,22 @@ 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
  }
15
+ export type SvmComputeUnitPrice = "auto" | bigint;
14
16
  export interface DepositOptions {
17
+ guardianSetIndex?: number;
15
18
  directTransfer?: boolean;
16
19
  depositDeadline?: string;
20
+ svmComputeUnitPrice?: SvmComputeUnitPrice;
17
21
  }
18
22
  export interface ChainAdapter {
19
23
  readonly kind: WalletKind;
@@ -41,20 +45,20 @@ export declare class EvmChainAdapter implements ChainAdapter {
41
45
  gaslessSteps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, quote: GaslessQuote): Promise<DepositStep[]>;
42
46
  }
43
47
  export declare class SvmChainAdapter implements ChainAdapter {
48
+ private client;
44
49
  private chain;
45
- private client?;
46
50
  readonly kind = "svm";
47
- constructor(chain: ChainStatus, client?: BlinkClient | undefined);
51
+ constructor(client: BlinkClient, chain: ChainStatus);
48
52
  balance(owner: string, token: string): Promise<bigint>;
49
53
  canPayGas(owner: string): Promise<boolean>;
50
54
  steps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, options?: DepositOptions): Promise<DepositStep[]>;
51
55
  gaslessSteps(wallet: ConnectedWallet, quoteHash: string, params: OrderParams, quote: GaslessQuote): Promise<DepositStep[]>;
52
56
  }
53
57
  export declare class SuiChainAdapter implements ChainAdapter {
58
+ private client;
54
59
  private chain;
55
- private client?;
56
60
  readonly kind = "sui";
57
- constructor(chain: ChainStatus, client?: BlinkClient | undefined);
61
+ constructor(client: BlinkClient, chain: ChainStatus);
58
62
  balance(owner: string, token: string): Promise<bigint>;
59
63
  canPayGas(owner: string): Promise<boolean>;
60
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,14 +18,59 @@ 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}`);
23
61
  }
24
62
  return chain.rpcUrl;
25
63
  };
64
+ const svmComputeUnitPriceMicroLamports = async (client, chain, price) => {
65
+ if (price !== "auto") {
66
+ return price;
67
+ }
68
+ const fee = await client.blink.getPriorityFee({ chain });
69
+ return fee.computeUnitPriceMicroLamports;
70
+ };
26
71
  const NATIVE_EVM_TOKEN = "0x0000000000000000000000000000000000000000";
27
72
  const SVM_MIN_FEE_LAMPORTS = 2000000n;
73
+ const SVM_GASLESS_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 10000n;
28
74
  const SUI_MIN_FEE_MIST = 20000000n;
29
75
  export class InsufficientGasError extends Error {
30
76
  required;
@@ -91,6 +137,9 @@ export class EvmChainAdapter {
91
137
  ...call,
92
138
  });
93
139
  };
140
+ const registry = this.client.contracts;
141
+ const guardianSetIndex = options?.guardianSetIndex;
142
+ await registry.assertRegistered(params.srcChain, params.routerAddress, guardianSetIndex);
94
143
  const base = {
95
144
  quoteHash,
96
145
  params,
@@ -102,16 +151,17 @@ export class EvmChainAdapter {
102
151
  await this.assertCanPay(wallet.address, fee);
103
152
  return [{ kind: "deposit", fee, run: () => send(call) }];
104
153
  }
105
- const request = {
106
- ...base,
107
- swap: isSourceSwap(params)
108
- ? await this.client.blink.getSwapInstructions({ quoteHash })
109
- : undefined,
110
- router: this.chain.router,
111
- };
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 };
112
162
  const call = buildEvmDepositCall(request);
113
163
  const target = call.to;
114
- const permitTarget = request.swap ? target : this.chain.router;
164
+ const permitTarget = swap ? target : router;
115
165
  const deposit = { kind: "deposit", run: () => send(call) };
116
166
  if (native) {
117
167
  deposit.fee = await feeOf(call);
@@ -211,10 +261,7 @@ export class EvmChainAdapter {
211
261
  }
212
262
  assertQuoteHash(quoteHash, params);
213
263
  assertDepositDeadline(quote.depositDeadline);
214
- const spender = this.chain.router;
215
- if (!spender) {
216
- throw new Error(`no router configured for ${this.chain.chain}`);
217
- }
264
+ const { router: spender } = await this.client.contracts.contracts(params.srcChain, quote.guardianSetIndex);
218
265
  const rpcUrl = rpcUrlOf(this.chain);
219
266
  const amountIn = BigInt(params.amountIn);
220
267
  if (isNativeEvmToken(params.srcToken)) {
@@ -281,12 +328,12 @@ export class EvmChainAdapter {
281
328
  }
282
329
  }
283
330
  export class SvmChainAdapter {
284
- chain;
285
331
  client;
332
+ chain;
286
333
  kind = "svm";
287
- constructor(chain, client) {
288
- this.chain = chain;
334
+ constructor(client, chain) {
289
335
  this.client = client;
336
+ this.chain = chain;
290
337
  }
291
338
  balance(owner, token) {
292
339
  return svmBalance(rpcUrlOf(this.chain), token, owner);
@@ -301,15 +348,12 @@ export class SvmChainAdapter {
301
348
  }
302
349
  assertQuoteHash(quoteHash, params);
303
350
  assertDepositDeadline(options?.depositDeadline);
304
- if (!this.chain.treasury) {
305
- throw new Error("svm deposits are unavailable");
306
- }
351
+ const client = this.client;
352
+ const registry = client.contracts;
353
+ const guardianSetIndex = options?.guardianSetIndex;
307
354
  const rpcUrl = rpcUrlOf(this.chain);
355
+ const computeUnitPrice = options?.svmComputeUnitPrice ?? "auto";
308
356
  if (isSourceSwap(params)) {
309
- const client = this.client;
310
- if (!client) {
311
- throw new Error("svm swap deposits need an api client");
312
- }
313
357
  return [
314
358
  {
315
359
  kind: "deposit",
@@ -320,9 +364,10 @@ export class SvmChainAdapter {
320
364
  if (instructions.payload.case !== "svm") {
321
365
  throw new Error("expected svm swap instructions");
322
366
  }
367
+ await registry.assertRegistered(params.srcChain, instructions.routerAddress, guardianSetIndex);
368
+ const { swapPrograms } = await registry.contracts(params.srcChain, guardianSetIndex);
323
369
  const transaction = await buildSvmSwapDeposit({
324
370
  rpcUrl,
325
- chain: this.chain.chain,
326
371
  programId: instructions.routerAddress,
327
372
  owner: wallet.address,
328
373
  srcToken: params.srcToken,
@@ -331,7 +376,8 @@ export class SvmChainAdapter {
331
376
  minBridgeOut: BigInt(params.minBridgeOut),
332
377
  quoteHash,
333
378
  swap: instructions.payload.value,
334
- swapProgramSigner: this.chain.treasury,
379
+ computeUnitPriceMicroLamports: await svmComputeUnitPriceMicroLamports(client, this.chain.chain, computeUnitPrice),
380
+ swapPrograms,
335
381
  });
336
382
  return signAndSendSvmTransaction(wallet.wallet, wallet.address, transaction);
337
383
  },
@@ -342,6 +388,7 @@ export class SvmChainAdapter {
342
388
  {
343
389
  kind: "deposit",
344
390
  run: async () => {
391
+ await registry.assertRegistered(params.srcChain, params.routerAddress, guardianSetIndex);
345
392
  const transaction = await buildSvmDeposit({
346
393
  rpcUrl,
347
394
  programId: params.routerAddress,
@@ -349,6 +396,7 @@ export class SvmChainAdapter {
349
396
  token: params.srcToken,
350
397
  amount: BigInt(params.amountIn),
351
398
  quoteHash,
399
+ computeUnitPriceMicroLamports: await svmComputeUnitPriceMicroLamports(client, this.chain.chain, computeUnitPrice),
352
400
  });
353
401
  return signAndSendSvmTransaction(wallet.wallet, wallet.address, transaction);
354
402
  },
@@ -371,15 +419,13 @@ export class SvmChainAdapter {
371
419
  if (!sponsor) {
372
420
  throw new Error("gasless deposits are unavailable on svm");
373
421
  }
374
- if (!this.client) {
375
- throw new Error("gasless deposits need an api client");
376
- }
377
422
  const client = this.client;
378
423
  const rpcUrl = rpcUrlOf(this.chain);
379
424
  return [
380
425
  {
381
426
  kind: "deposit",
382
427
  run: async () => {
428
+ await client.contracts.assertRegistered(params.srcChain, params.routerAddress, quote.guardianSetIndex);
383
429
  const transaction = await buildSvmDeposit({
384
430
  rpcUrl,
385
431
  programId: params.routerAddress,
@@ -387,6 +433,7 @@ export class SvmChainAdapter {
387
433
  token: params.srcToken,
388
434
  amount: BigInt(params.amountIn),
389
435
  quoteHash,
436
+ computeUnitPriceMicroLamports: SVM_GASLESS_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
390
437
  feePayer: sponsor,
391
438
  });
392
439
  const signed = await signSvmTransaction(wallet.wallet, wallet.address, transaction);
@@ -404,12 +451,12 @@ export class SvmChainAdapter {
404
451
  }
405
452
  }
406
453
  export class SuiChainAdapter {
407
- chain;
408
454
  client;
455
+ chain;
409
456
  kind = "sui";
410
- constructor(chain, client) {
411
- this.chain = chain;
457
+ constructor(client, chain) {
412
458
  this.client = client;
459
+ this.chain = chain;
413
460
  }
414
461
  balance(owner, token) {
415
462
  return suiBalance(rpcUrlOf(this.chain), token, owner);
@@ -424,20 +471,17 @@ export class SuiChainAdapter {
424
471
  }
425
472
  assertQuoteHash(quoteHash, params);
426
473
  assertDepositDeadline(options?.depositDeadline);
427
- const escrow = this.chain.escrow;
428
- if (!escrow) {
429
- throw new Error("sui deposits are unavailable");
430
- }
431
- if (isSourceSwap(params) && !this.client) {
432
- throw new Error("sui swap deposits need an api client");
433
- }
434
474
  const client = this.client;
435
- const rpcUrl = rpcUrlOf(this.chain);
475
+ const chain = this.chain;
436
476
  return [
437
477
  {
438
478
  kind: "deposit",
439
- 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
+ })),
440
483
  },
484
+ ...sourceSwapStep(client, quoteHash, params),
441
485
  ];
442
486
  }
443
487
  async gaslessSteps(wallet, quoteHash, params, quote) {
@@ -446,25 +490,22 @@ export class SuiChainAdapter {
446
490
  }
447
491
  assertQuoteHash(quoteHash, params);
448
492
  assertDepositDeadline(quote.depositDeadline);
449
- const escrow = this.chain.escrow;
450
- if (!escrow) {
451
- throw new Error("sui deposits are unavailable");
452
- }
453
493
  const sponsor = quote.sponsorAddress;
454
494
  const gasBudget = quote.sponsorGasBudget;
455
495
  if (!sponsor || gasBudget === undefined) {
456
496
  throw new Error("gasless deposits are unavailable on sui");
457
497
  }
458
- if (!this.client) {
459
- throw new Error("gasless deposits need an api client");
460
- }
461
498
  const client = this.client;
462
- const rpcUrl = rpcUrlOf(this.chain);
499
+ const chain = this.chain;
500
+ const rpcUrl = rpcUrlOf(chain);
463
501
  return [
464
502
  {
465
503
  kind: "deposit",
466
504
  run: async () => {
467
- 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);
468
509
  const signed = await signSuiTransaction(wallet.wallet, wallet.address, transaction);
469
510
  await client.blink.submitGaslessOrder({
470
511
  quoteHash,
@@ -476,10 +517,18 @@ export class SuiChainAdapter {
476
517
  return trackGaslessSubmission(client, quoteHash);
477
518
  },
478
519
  },
520
+ ...sourceSwapStep(client, quoteHash, params),
479
521
  ];
480
522
  }
481
523
  }
482
- 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
+ }
483
532
  if (!isSourceSwap(params)) {
484
533
  return buildSuiDeposit({
485
534
  rpcUrl,
@@ -491,32 +540,29 @@ const suiDepositTransaction = async (client, rpcUrl, escrow, owner, quoteHash, p
491
540
  quoteHash,
492
541
  });
493
542
  }
494
- if (!client) {
495
- throw new Error("sui swap deposits need an api client");
496
- }
497
- const instructions = await client.blink.getSwapInstructions({ quoteHash });
498
- if (instructions.payload.case !== "sui") {
499
- throw new Error("expected sui swap instructions");
543
+ if (!chain.swapExecutor) {
544
+ throw new Error("sui source swaps are unavailable");
500
545
  }
501
- return buildSuiSwapDeposit({
546
+ return buildSuiSourceSwapOpen({
502
547
  rpcUrl,
503
548
  packageId: params.routerAddress,
504
549
  escrowObject: escrow,
550
+ swapExecutor: chain.swapExecutor,
505
551
  owner,
506
552
  srcToken: params.srcToken,
507
553
  bridgeToken: params.bridgeTokenSrc,
508
554
  amountIn: BigInt(params.amountIn),
509
555
  minBridgeOut: BigInt(params.minBridgeOut),
510
556
  quoteHash,
511
- swap: instructions.payload.value,
557
+ gasless,
512
558
  });
513
559
  };
514
560
  export const createChainAdapter = (client, chain, chainId) => {
515
561
  if (chain.vm === "svm") {
516
- return new SvmChainAdapter(chain, client);
562
+ return new SvmChainAdapter(client, chain);
517
563
  }
518
564
  if (chain.vm === "sui") {
519
- return new SuiChainAdapter(chain, client);
565
+ return new SuiChainAdapter(client, chain);
520
566
  }
521
567
  if (chainId === undefined) {
522
568
  throw new Error(`unknown chain id for ${chain.chain}`);