@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/dist/sui.js CHANGED
@@ -1,7 +1,6 @@
1
- import { bcs } from "@mysten/sui/bcs";
2
1
  import { SuiGrpcClient } from "@mysten/sui/grpc";
3
2
  import { Transaction, } from "@mysten/sui/transactions";
4
- import { fromBase64, normalizeStructTag, normalizeSuiAddress, toHex, } from "@mysten/sui/utils";
3
+ import { normalizeStructTag } from "@mysten/sui/utils";
5
4
  export const SUI_COIN_TYPE = "0x2::sui::SUI";
6
5
  export const isNativeSuiToken = (address) => address === SUI_COIN_TYPE;
7
6
  const client = (rpcUrl) => new SuiGrpcClient({ network: "mainnet", baseUrl: rpcUrl });
@@ -86,183 +85,62 @@ export const sponsorSuiTransaction = async (rpcUrl, transaction, sponsor, maxGas
86
85
  transaction.setGasBudget(needed < maxGasBudget ? needed : maxGasBudget);
87
86
  return Transaction.from(await transaction.build({ client: rpc }));
88
87
  };
89
- const rejectSwap = (reason) => {
90
- throw new Error(`swap instructions rejected: ${reason}`);
91
- };
92
- export const suiSwapFragmentOf = (swap) => ({
93
- fragment: bcs.ProgrammableTransaction.parse(fromBase64(swap.fragment)),
94
- coinOut: swap.coinOutResult === undefined
95
- ? { $kind: "Result", Result: swap.coinOutCommand }
96
- : {
97
- $kind: "NestedResult",
98
- NestedResult: [swap.coinOutCommand, swap.coinOutResult],
99
- },
100
- });
101
- const inputKind = (input) => input.$kind === "Object" ? input.Object.$kind : input.$kind;
102
- const commandArguments = (command) => {
103
- switch (command.$kind) {
104
- case "MoveCall":
105
- return command.MoveCall.arguments;
106
- case "TransferObjects":
107
- return [
108
- ...command.TransferObjects.objects,
109
- command.TransferObjects.address,
110
- ];
111
- case "SplitCoins":
112
- return [command.SplitCoins.coin, ...command.SplitCoins.amounts];
113
- case "MergeCoins":
114
- return [command.MergeCoins.destination, ...command.MergeCoins.sources];
115
- case "MakeMoveVec":
116
- return command.MakeMoveVec.elements;
117
- default:
118
- return rejectSwap(`command ${command.$kind} is not allowed`);
119
- }
120
- };
121
- const referencedCommand = (arg) => arg.$kind === "Result"
122
- ? arg.Result
123
- : arg.$kind === "NestedResult"
124
- ? arg.NestedResult[0]
125
- : undefined;
126
- const isSenderInput = (fragment, arg, sender) => {
127
- if (arg.$kind !== "Input") {
128
- return false;
129
- }
130
- const input = fragment.inputs[arg.Input];
131
- if (input?.$kind !== "Pure") {
132
- return false;
133
- }
134
- const bytes = fromBase64(input.Pure.bytes);
135
- return (bytes.length === 32 &&
136
- normalizeSuiAddress(toHex(bytes)) === normalizeSuiAddress(sender));
137
- };
138
- const isCoinInStandIn = (command, srcToken) => command?.$kind === "MoveCall" &&
139
- normalizeSuiAddress(command.MoveCall.package) ===
140
- normalizeSuiAddress("0x2") &&
141
- command.MoveCall.module === "coin" &&
142
- command.MoveCall.function === "zero" &&
143
- command.MoveCall.arguments.length === 0 &&
144
- command.MoveCall.typeArguments.length === 1 &&
145
- normalizeStructTag(command.MoveCall.typeArguments[0]) ===
146
- normalizeStructTag(srcToken);
147
- export const checkSuiSwapFragment = (fragment, coinOut, rules) => {
148
- if (!isCoinInStandIn(fragment.commands[0], rules.srcToken)) {
149
- rejectSwap("first command is not the input coin stand-in");
150
- }
151
- for (const input of fragment.inputs) {
152
- if (input.$kind !== "Pure" &&
153
- !(input.$kind === "Object" && input.Object.$kind === "SharedObject")) {
154
- rejectSwap(`input ${inputKind(input)} is not allowed`);
155
- }
156
- }
157
- fragment.commands.forEach((command, index) => {
158
- if (index === 0) {
159
- return;
160
- }
161
- for (const arg of commandArguments(command)) {
162
- if (arg.$kind === "GasCoin") {
163
- rejectSwap("gas coin is not allowed");
164
- }
165
- if (arg.$kind === "Input" && arg.Input >= fragment.inputs.length) {
166
- rejectSwap("argument references a missing input");
167
- }
168
- if (arg.$kind === "NestedResult" && arg.NestedResult[0] === 0) {
169
- rejectSwap("nested result of the input coin stand-in");
170
- }
171
- const target = referencedCommand(arg);
172
- if (target !== undefined && target >= index) {
173
- rejectSwap("argument references a later command");
174
- }
175
- }
176
- if (command.$kind === "TransferObjects" &&
177
- !isSenderInput(fragment, command.TransferObjects.address, rules.sender)) {
178
- rejectSwap("transfer recipient is not the sender");
179
- }
88
+ const SOURCE_SWAP_WINDOW_MS = 300000n;
89
+ const SOURCE_SWAP_ALLOWANCE_NAME = "blink source swap";
90
+ export const suiSourceSwapExpiry = async (rpcUrl) => {
91
+ const { response } = await client(rpcUrl).ledgerService.getCheckpoint({
92
+ checkpointId: { oneofKind: undefined },
93
+ readMask: { paths: ["summary.timestamp"] },
180
94
  });
181
- const outCommand = referencedCommand(coinOut);
182
- if (outCommand === undefined ||
183
- outCommand === 0 ||
184
- outCommand >= fragment.commands.length) {
185
- rejectSwap("output coin does not reference a swap command");
95
+ const timestamp = response.checkpoint?.summary?.timestamp;
96
+ if (!timestamp) {
97
+ throw new Error("sui checkpoint has no timestamp");
186
98
  }
99
+ const nowMs = timestamp.seconds * 1000n + BigInt(Math.floor(timestamp.nanos / 1_000_000));
100
+ return nowMs + SOURCE_SWAP_WINDOW_MS;
187
101
  };
188
- export const appendSwapFragment = (tx, fragment, payment, coinOut) => {
189
- const inputs = fragment.inputs.map((input) => {
190
- if (input.$kind === "Pure") {
191
- return tx.pure(fromBase64(input.Pure.bytes));
192
- }
193
- if (input.$kind === "Object" && input.Object.$kind === "SharedObject") {
194
- return tx.sharedObjectRef(input.Object.SharedObject);
195
- }
196
- return rejectSwap(`input ${inputKind(input)} is not allowed`);
197
- });
198
- const results = [];
199
- const resolve = (arg) => {
200
- switch (arg.$kind) {
201
- case "Input":
202
- return inputs[arg.Input];
203
- case "Result":
204
- return arg.Result === 0 ? payment : results[arg.Result];
205
- case "NestedResult":
206
- return results[arg.NestedResult[0]][arg.NestedResult[1]];
207
- default:
208
- return rejectSwap("gas coin is not allowed");
209
- }
210
- };
211
- fragment.commands.forEach((command, index) => {
212
- if (index === 0) {
213
- return;
214
- }
215
- switch (command.$kind) {
216
- case "MoveCall":
217
- results[index] = tx.moveCall({
218
- target: `${command.MoveCall.package}::${command.MoveCall.module}::${command.MoveCall.function}`,
219
- typeArguments: command.MoveCall.typeArguments,
220
- arguments: command.MoveCall.arguments.map(resolve),
221
- });
222
- return;
223
- case "TransferObjects":
224
- results[index] = tx.transferObjects(command.TransferObjects.objects.map(resolve), resolve(command.TransferObjects.address));
225
- return;
226
- case "SplitCoins":
227
- results[index] = tx.splitCoins(resolve(command.SplitCoins.coin), command.SplitCoins.amounts.map(resolve));
228
- return;
229
- case "MergeCoins":
230
- results[index] = tx.mergeCoins(resolve(command.MergeCoins.destination), command.MergeCoins.sources.map(resolve));
231
- return;
232
- case "MakeMoveVec":
233
- results[index] = tx.makeMoveVec({
234
- type: command.MakeMoveVec.type ?? undefined,
235
- elements: command.MakeMoveVec.elements.map(resolve),
236
- });
237
- return;
238
- default:
239
- rejectSwap(`command ${command.$kind} is not allowed`);
240
- }
102
+ export const addSuiSourceSwapOpenCall = (tx, call) => {
103
+ if (call.payment) {
104
+ tx.moveCall({
105
+ target: "0x2::coin::send_funds",
106
+ typeArguments: [call.srcToken],
107
+ arguments: [call.payment, tx.pure.address(call.sender)],
108
+ });
109
+ }
110
+ const noRateLimit = tx.moveCall({
111
+ target: "0x1::option::none",
112
+ typeArguments: ["0x2::allowance::RateLimit"],
241
113
  });
242
- return resolve(coinOut);
243
- };
244
- export const addSuiSwapDepositCall = (tx, call) => {
245
- const { fragment, coinOut } = suiSwapFragmentOf(call.swap);
246
- checkSuiSwapFragment(fragment, coinOut, {
247
- sender: call.sender,
248
- srcToken: call.srcToken,
114
+ const proposal = tx.moveCall({
115
+ target: "0x2::allowance::propose_for_app",
116
+ typeArguments: [
117
+ `0x2::balance::Balance<${call.srcToken}>`,
118
+ `${call.packageId}::blink::SourceSwapFunding`,
119
+ ],
120
+ arguments: [
121
+ tx.pure.string(SOURCE_SWAP_ALLOWANCE_NAME),
122
+ tx.pure.address(call.swapExecutor),
123
+ tx.pure.option("u256", call.amountIn),
124
+ tx.pure.option("u64", null),
125
+ tx.pure.option("u64", call.expiresAtMs),
126
+ noRateLimit,
127
+ ],
249
128
  });
250
- const bridged = appendSwapFragment(tx, fragment, call.payment, coinOut);
251
129
  tx.moveCall({
252
- target: `${call.packageId}::blink::deposit_swap`,
130
+ target: `${call.packageId}::blink::open_source_swap`,
253
131
  typeArguments: [call.srcToken, call.bridgeToken],
254
132
  arguments: [
255
133
  tx.object(call.escrowObject),
134
+ proposal,
256
135
  tx.pure.vector("u8", hexBytes(call.quoteHash)),
257
- bridged,
258
- tx.pure.u64(call.amountIn),
259
136
  tx.pure.u64(call.minBridgeOut),
137
+ tx.object.clock(),
260
138
  ],
261
139
  });
262
140
  };
263
141
  const isSuiCoinType = (coinType) => normalizeStructTag(coinType) === normalizeStructTag(SUI_COIN_TYPE);
264
- const addPayment = async (tx, rpcUrl, owner, coinType, amount) => {
265
- if (isSuiCoinType(coinType)) {
142
+ const addPayment = async (tx, rpcUrl, owner, coinType, amount, gasless) => {
143
+ if (isSuiCoinType(coinType) && !gasless) {
266
144
  const [payment] = tx.splitCoins(tx.gas, [amount]);
267
145
  return payment;
268
146
  }
@@ -274,20 +152,29 @@ const addPayment = async (tx, rpcUrl, owner, coinType, amount) => {
274
152
  const [payment] = tx.splitCoins(primary, [amount]);
275
153
  return payment;
276
154
  };
277
- export const buildSuiSwapDeposit = async (request) => {
155
+ const suiAddressBalance = async (rpcUrl, owner, coinType) => {
156
+ const { balance } = await client(rpcUrl).getBalance({ owner, coinType });
157
+ return BigInt(balance.addressBalance);
158
+ };
159
+ export const buildSuiSourceSwapOpen = async (request) => {
278
160
  const tx = new Transaction();
279
161
  tx.setSender(request.owner);
280
- const payment = await addPayment(tx, request.rpcUrl, request.owner, request.srcToken, request.amountIn);
281
- addSuiSwapDepositCall(tx, {
162
+ const shortfall = request.amountIn -
163
+ (await suiAddressBalance(request.rpcUrl, request.owner, request.srcToken));
164
+ const payment = shortfall > 0n
165
+ ? await addPayment(tx, request.rpcUrl, request.owner, request.srcToken, shortfall, request.gasless)
166
+ : undefined;
167
+ addSuiSourceSwapOpenCall(tx, {
282
168
  packageId: request.packageId,
283
169
  escrowObject: request.escrowObject,
170
+ swapExecutor: request.swapExecutor,
284
171
  sender: request.owner,
285
172
  srcToken: request.srcToken,
286
173
  bridgeToken: request.bridgeToken,
287
174
  amountIn: request.amountIn,
288
175
  minBridgeOut: request.minBridgeOut,
289
176
  quoteHash: request.quoteHash,
290
- swap: request.swap,
177
+ expiresAtMs: await suiSourceSwapExpiry(request.rpcUrl),
291
178
  payment,
292
179
  });
293
180
  return tx;
package/dist/svm.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AddressLookupTableAccount, PublicKey, TransactionInstruction } from "@solana/web3.js";
2
2
  import type { SvmSwapInstructions } from "./gen/blink/v1/api_pb.js";
3
- export declare const DEFAULT_SWAP_PROGRAMS: PublicKey[];
4
3
  export declare const isNativeSvmToken: (address: string) => boolean;
4
+ export declare const svmSwapAuthority: (programId: PublicKey, owner: PublicKey, quoteHash: string) => PublicKey;
5
5
  export declare const svmBalance: (rpcUrl: string, token: string, owner: string) => Promise<bigint>;
6
6
  export interface SvmDepositRequest {
7
7
  rpcUrl: string;
@@ -13,10 +13,8 @@ export interface SvmDepositRequest {
13
13
  computeUnitPriceMicroLamports: bigint;
14
14
  feePayer?: string;
15
15
  }
16
- export declare const resolveSwapPrograms: (chain: string, swap: SvmSwapInstructions, expectedSigner: string | undefined) => PublicKey[];
17
16
  export interface SvmSwapDepositRequest {
18
17
  rpcUrl: string;
19
- chain: string;
20
18
  programId: string;
21
19
  owner: string;
22
20
  srcToken: string;
@@ -26,7 +24,7 @@ export interface SvmSwapDepositRequest {
26
24
  quoteHash: string;
27
25
  swap: SvmSwapInstructions;
28
26
  computeUnitPriceMicroLamports: bigint;
29
- swapProgramSigner?: string;
27
+ swapPrograms: string[];
30
28
  }
31
29
  export interface SvmDepositInstructions {
32
30
  instructions: TransactionInstruction[];