@mayanfinance/blink-sdk 0.1.8
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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/amount.d.ts +4 -0
- package/dist/amount.js +32 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +32 -0
- package/dist/deposit.d.ts +64 -0
- package/dist/deposit.js +526 -0
- package/dist/evm.d.ts +237 -0
- package/dist/evm.js +679 -0
- package/dist/gen/blink/v1/admin_pb.d.ts +1338 -0
- package/dist/gen/blink/v1/admin_pb.js +234 -0
- package/dist/gen/blink/v1/api_pb.d.ts +1811 -0
- package/dist/gen/blink/v1/api_pb.js +294 -0
- package/dist/gen/blink/v1/partner_pb.d.ts +1020 -0
- package/dist/gen/blink/v1/partner_pb.js +247 -0
- package/dist/gen/blink/v1/ratelimit_pb.d.ts +10 -0
- package/dist/gen/blink/v1/ratelimit_pb.js +13 -0
- package/dist/gen/google/api/annotations_pb.d.ts +11 -0
- package/dist/gen/google/api/annotations_pb.js +14 -0
- package/dist/gen/google/api/http_pb.d.ts +111 -0
- package/dist/gen/google/api/http_pb.js +23 -0
- package/dist/hash.d.ts +7 -0
- package/dist/hash.js +76 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/order.d.ts +42 -0
- package/dist/order.js +173 -0
- package/dist/sui.d.ts +74 -0
- package/dist/sui.js +310 -0
- package/dist/svm.d.ts +36 -0
- package/dist/svm.js +504 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/wallets.d.ts +84 -0
- package/dist/wallets.js +409 -0
- package/package.json +62 -0
package/dist/order.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { fromJson } from "@bufbuild/protobuf";
|
|
2
|
+
import { buildEvmDepositCall, } from "./evm.js";
|
|
3
|
+
import { QuoteResponseSchema, } from "./gen/blink/v1/api_pb.js";
|
|
4
|
+
import { verifyQuote } from "./hash.js";
|
|
5
|
+
import { addSuiDepositCall, addSuiSwapDepositCall, } from "./sui.js";
|
|
6
|
+
import { buildSvmDepositInstructions, buildSvmSwapDepositInstructions, } from "./svm.js";
|
|
7
|
+
export const quoteOf = (response) => response.quote.value;
|
|
8
|
+
export const isSameChainSwap = (response) => response.quote.case === "sameChainSwap";
|
|
9
|
+
export const depositDeadlineOf = (quote) => {
|
|
10
|
+
if (!quote.depositDeadline) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const parsed = new Date(quote.depositDeadline);
|
|
14
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
15
|
+
};
|
|
16
|
+
export const isQuoteExpired = (quote) => {
|
|
17
|
+
const deadline = depositDeadlineOf(quote);
|
|
18
|
+
return deadline !== undefined && deadline.getTime() <= Date.now();
|
|
19
|
+
};
|
|
20
|
+
export const assertDepositDeadline = (depositDeadline) => {
|
|
21
|
+
if (!depositDeadline) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const deadline = new Date(depositDeadline);
|
|
25
|
+
if (!Number.isNaN(deadline.getTime()) && deadline.getTime() <= Date.now()) {
|
|
26
|
+
throw new Error("quote deposit deadline passed, request a new quote");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const OUTCOMES = {
|
|
30
|
+
fulfilled: "fulfilled",
|
|
31
|
+
refunded: "refunded",
|
|
32
|
+
refund_skipped: "failed",
|
|
33
|
+
deposit_missing: "failed",
|
|
34
|
+
refund_failed: "failed",
|
|
35
|
+
unsupported_deposit: "failed",
|
|
36
|
+
};
|
|
37
|
+
export const orderOutcome = (state) => OUTCOMES[state] ?? "pending";
|
|
38
|
+
export const isTerminalState = (state) => state in OUTCOMES;
|
|
39
|
+
export const isGuaranteedTradeType = (tradeType) => tradeType === "exact_input_guaranteed_output" || tradeType === "exact_output";
|
|
40
|
+
export const explorerTxUrl = (template, tx) => template && tx ? template.replace("{tx}", tx) : undefined;
|
|
41
|
+
export const explorerAddressUrl = (template, address) => template && address ? template.replace("{address}", address) : undefined;
|
|
42
|
+
export const isSourceSwap = (params) => params.srcToken !== params.bridgeTokenSrc;
|
|
43
|
+
export const decodeCrossChainQuote = (json) => {
|
|
44
|
+
const response = fromJson(QuoteResponseSchema, json);
|
|
45
|
+
if (response.quote.case !== "crossChain") {
|
|
46
|
+
throw new Error("expected a cross-chain quote");
|
|
47
|
+
}
|
|
48
|
+
const quote = response.quote.value;
|
|
49
|
+
verifyQuote(quote);
|
|
50
|
+
assertDepositDeadline(quote.depositDeadline);
|
|
51
|
+
return quote;
|
|
52
|
+
};
|
|
53
|
+
const paramsOf = (quote) => {
|
|
54
|
+
if (!quote.orderParams) {
|
|
55
|
+
throw new Error("quote has no order params");
|
|
56
|
+
}
|
|
57
|
+
return quote.orderParams;
|
|
58
|
+
};
|
|
59
|
+
const sameAddress = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
60
|
+
const referrerKey = (r) => `${r.address.toLowerCase()}:${r.bps}`;
|
|
61
|
+
const sameReferrers = (a, b) => {
|
|
62
|
+
if (a.length !== b.length) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const keys = new Set(a.map(referrerKey));
|
|
66
|
+
return b.every((r) => keys.has(referrerKey(r)));
|
|
67
|
+
};
|
|
68
|
+
export const assertOrderMatches = (quote, expected) => {
|
|
69
|
+
const params = paramsOf(quote);
|
|
70
|
+
if (params.srcChain !== expected.srcChain ||
|
|
71
|
+
params.dstChain !== expected.dstChain) {
|
|
72
|
+
throw new Error("order chains do not match the quote");
|
|
73
|
+
}
|
|
74
|
+
if (BigInt(params.amountIn) !== expected.amountIn) {
|
|
75
|
+
throw new Error("order amount does not match the quote");
|
|
76
|
+
}
|
|
77
|
+
if (!sameAddress(params.recipient, expected.recipient)) {
|
|
78
|
+
throw new Error("order recipient does not match the destination address");
|
|
79
|
+
}
|
|
80
|
+
if (!sameAddress(params.refundAddress, expected.refundAddress)) {
|
|
81
|
+
throw new Error("order refund address does not match");
|
|
82
|
+
}
|
|
83
|
+
if (expected.minAmountOut !== undefined &&
|
|
84
|
+
BigInt(params.minAmountOut) < expected.minAmountOut) {
|
|
85
|
+
throw new Error("order minimum output is below the quoted minimum, request a new quote");
|
|
86
|
+
}
|
|
87
|
+
if (expected.referrers !== undefined &&
|
|
88
|
+
!sameReferrers(params.referrers, expected.referrers)) {
|
|
89
|
+
throw new Error("order referrers do not match the requested referrers");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
export const evmOrderDepositCall = (order, signature) => buildEvmDepositCall({
|
|
93
|
+
quoteHash: order.quote.quoteHash,
|
|
94
|
+
params: paramsOf(order.quote),
|
|
95
|
+
swap: order.swap,
|
|
96
|
+
directTransfer: order.quote.directTransfer,
|
|
97
|
+
router: order.chain?.router,
|
|
98
|
+
}, signature);
|
|
99
|
+
export const svmOrderDepositInstructions = async (order, rpcUrl, owner) => {
|
|
100
|
+
const params = paramsOf(order.quote);
|
|
101
|
+
const amount = BigInt(params.amountIn);
|
|
102
|
+
if (!isSourceSwap(params)) {
|
|
103
|
+
return {
|
|
104
|
+
instructions: await buildSvmDepositInstructions({
|
|
105
|
+
rpcUrl,
|
|
106
|
+
programId: params.routerAddress,
|
|
107
|
+
owner,
|
|
108
|
+
token: params.srcToken,
|
|
109
|
+
amount,
|
|
110
|
+
quoteHash: order.quote.quoteHash,
|
|
111
|
+
}),
|
|
112
|
+
lookupTables: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const swap = order.swap;
|
|
116
|
+
if (!swap || swap.payload.case !== "svm") {
|
|
117
|
+
throw new Error("expected svm swap instructions");
|
|
118
|
+
}
|
|
119
|
+
const swapProgramSigner = order.chain?.treasury;
|
|
120
|
+
if (!swapProgramSigner) {
|
|
121
|
+
throw new Error("svm deposits are unavailable");
|
|
122
|
+
}
|
|
123
|
+
return buildSvmSwapDepositInstructions({
|
|
124
|
+
rpcUrl,
|
|
125
|
+
chain: params.srcChain,
|
|
126
|
+
programId: swap.routerAddress,
|
|
127
|
+
owner,
|
|
128
|
+
srcToken: params.srcToken,
|
|
129
|
+
bridgeToken: params.bridgeTokenSrc,
|
|
130
|
+
amount,
|
|
131
|
+
minBridgeOut: BigInt(params.minBridgeOut),
|
|
132
|
+
quoteHash: order.quote.quoteHash,
|
|
133
|
+
swap: swap.payload.value,
|
|
134
|
+
swapProgramSigner,
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
export const suiOrderDepositCall = (order, tx, payment) => {
|
|
138
|
+
const params = paramsOf(order.quote);
|
|
139
|
+
const escrow = order.chain?.escrow;
|
|
140
|
+
if (!escrow) {
|
|
141
|
+
throw new Error("sui deposits are unavailable");
|
|
142
|
+
}
|
|
143
|
+
if (isSourceSwap(params)) {
|
|
144
|
+
const swap = order.swap;
|
|
145
|
+
if (!swap || swap.payload.case !== "sui") {
|
|
146
|
+
throw new Error("expected sui swap instructions");
|
|
147
|
+
}
|
|
148
|
+
const sender = tx.getData().sender;
|
|
149
|
+
if (!sender) {
|
|
150
|
+
throw new Error("sui swap deposits need the transaction sender set");
|
|
151
|
+
}
|
|
152
|
+
addSuiSwapDepositCall(tx, {
|
|
153
|
+
packageId: params.routerAddress,
|
|
154
|
+
escrowObject: escrow,
|
|
155
|
+
sender,
|
|
156
|
+
srcToken: params.srcToken,
|
|
157
|
+
bridgeToken: params.bridgeTokenSrc,
|
|
158
|
+
amountIn: BigInt(params.amountIn),
|
|
159
|
+
minBridgeOut: BigInt(params.minBridgeOut),
|
|
160
|
+
quoteHash: order.quote.quoteHash,
|
|
161
|
+
swap: swap.payload.value,
|
|
162
|
+
payment,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
addSuiDepositCall(tx, {
|
|
167
|
+
packageId: params.routerAddress,
|
|
168
|
+
escrowObject: escrow,
|
|
169
|
+
coinType: params.srcToken,
|
|
170
|
+
quoteHash: order.quote.quoteHash,
|
|
171
|
+
payment,
|
|
172
|
+
});
|
|
173
|
+
};
|
package/dist/sui.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
2
|
+
import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions";
|
|
3
|
+
import type { SuiSwapInstructions } from "./gen/blink/v1/api_pb.js";
|
|
4
|
+
export declare const SUI_COIN_TYPE = "0x2::sui::SUI";
|
|
5
|
+
export declare const isNativeSuiToken: (address: string) => boolean;
|
|
6
|
+
export declare const suiBalance: (rpcUrl: string, coinType: string, owner: string) => Promise<bigint>;
|
|
7
|
+
export interface SuiDepositRequest {
|
|
8
|
+
rpcUrl: string;
|
|
9
|
+
packageId: string;
|
|
10
|
+
escrowObject: string;
|
|
11
|
+
owner: string;
|
|
12
|
+
coinType: string;
|
|
13
|
+
amount: bigint;
|
|
14
|
+
quoteHash: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SuiDepositCall {
|
|
17
|
+
packageId: string;
|
|
18
|
+
escrowObject: string;
|
|
19
|
+
coinType: string;
|
|
20
|
+
quoteHash: string;
|
|
21
|
+
payment: TransactionObjectArgument;
|
|
22
|
+
}
|
|
23
|
+
export declare const addSuiDepositCall: (tx: Transaction, call: SuiDepositCall) => void;
|
|
24
|
+
export declare const buildSuiDeposit: (request: SuiDepositRequest) => Promise<Transaction>;
|
|
25
|
+
export declare const sponsorSuiTransaction: (rpcUrl: string, transaction: Transaction, sponsor: string, maxGasBudget: bigint) => Promise<Transaction>;
|
|
26
|
+
export type SuiSwapFragment = typeof bcs.ProgrammableTransaction.$inferType;
|
|
27
|
+
export type SuiFragmentArgument = typeof bcs.Argument.$inferType;
|
|
28
|
+
export interface SuiSwapFragmentRules {
|
|
29
|
+
sender: string;
|
|
30
|
+
srcToken: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const suiSwapFragmentOf: (swap: SuiSwapInstructions) => {
|
|
33
|
+
fragment: SuiSwapFragment;
|
|
34
|
+
coinOut: SuiFragmentArgument;
|
|
35
|
+
};
|
|
36
|
+
export declare const checkSuiSwapFragment: (fragment: SuiSwapFragment, coinOut: SuiFragmentArgument, rules: SuiSwapFragmentRules) => void;
|
|
37
|
+
export declare const appendSwapFragment: (tx: Transaction, fragment: SuiSwapFragment, payment: TransactionObjectArgument, coinOut: SuiFragmentArgument) => TransactionObjectArgument;
|
|
38
|
+
export interface SuiSwapDepositCall {
|
|
39
|
+
packageId: string;
|
|
40
|
+
escrowObject: string;
|
|
41
|
+
sender: string;
|
|
42
|
+
srcToken: string;
|
|
43
|
+
bridgeToken: string;
|
|
44
|
+
amountIn: bigint;
|
|
45
|
+
minBridgeOut: bigint;
|
|
46
|
+
quoteHash: string;
|
|
47
|
+
swap: SuiSwapInstructions;
|
|
48
|
+
payment: TransactionObjectArgument;
|
|
49
|
+
}
|
|
50
|
+
export declare const addSuiSwapDepositCall: (tx: Transaction, call: SuiSwapDepositCall) => void;
|
|
51
|
+
export interface SuiSwapDepositRequest {
|
|
52
|
+
rpcUrl: string;
|
|
53
|
+
packageId: string;
|
|
54
|
+
escrowObject: string;
|
|
55
|
+
owner: string;
|
|
56
|
+
srcToken: string;
|
|
57
|
+
bridgeToken: string;
|
|
58
|
+
amountIn: bigint;
|
|
59
|
+
minBridgeOut: bigint;
|
|
60
|
+
quoteHash: string;
|
|
61
|
+
swap: SuiSwapInstructions;
|
|
62
|
+
}
|
|
63
|
+
export declare const buildSuiSwapDeposit: (request: SuiSwapDepositRequest) => Promise<Transaction>;
|
|
64
|
+
export interface SuiVoucherRedemptionRequest {
|
|
65
|
+
packageId: string;
|
|
66
|
+
escrowObject: string;
|
|
67
|
+
coinType: string;
|
|
68
|
+
id: string;
|
|
69
|
+
referrer: string;
|
|
70
|
+
amount: bigint;
|
|
71
|
+
signature: string;
|
|
72
|
+
sender: string;
|
|
73
|
+
}
|
|
74
|
+
export declare const buildSuiVoucherRedemption: (request: SuiVoucherRedemptionRequest) => Transaction;
|
package/dist/sui.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
2
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
3
|
+
import { Transaction, } from "@mysten/sui/transactions";
|
|
4
|
+
import { fromBase64, normalizeStructTag, normalizeSuiAddress, toHex, } from "@mysten/sui/utils";
|
|
5
|
+
export const SUI_COIN_TYPE = "0x2::sui::SUI";
|
|
6
|
+
export const isNativeSuiToken = (address) => address === SUI_COIN_TYPE;
|
|
7
|
+
const client = (rpcUrl) => new SuiGrpcClient({ network: "mainnet", baseUrl: rpcUrl });
|
|
8
|
+
const hexBytes = (hex) => {
|
|
9
|
+
const raw = hex.replace(/^0x/, "");
|
|
10
|
+
const out = new Uint8Array(raw.length / 2);
|
|
11
|
+
for (let i = 0; i < out.length; i++) {
|
|
12
|
+
out[i] = parseInt(raw.slice(i * 2, i * 2 + 2), 16);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
export const suiBalance = async (rpcUrl, coinType, owner) => {
|
|
17
|
+
const { balance } = await client(rpcUrl).getBalance({ owner, coinType });
|
|
18
|
+
return BigInt(balance.balance);
|
|
19
|
+
};
|
|
20
|
+
const collectCoins = async (rpc, owner, coinType, amount) => {
|
|
21
|
+
const ids = [];
|
|
22
|
+
let total = 0n;
|
|
23
|
+
let cursor;
|
|
24
|
+
for (;;) {
|
|
25
|
+
const page = await rpc.listCoins({ owner, coinType, cursor });
|
|
26
|
+
for (const coin of page.objects) {
|
|
27
|
+
ids.push(coin.objectId);
|
|
28
|
+
total += BigInt(coin.balance);
|
|
29
|
+
if (total >= amount) {
|
|
30
|
+
return ids;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!page.hasNextPage || !page.cursor) {
|
|
34
|
+
throw new Error(`insufficient ${coinType} balance for ${owner}`);
|
|
35
|
+
}
|
|
36
|
+
cursor = page.cursor;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const addSuiDepositCall = (tx, call) => {
|
|
40
|
+
tx.moveCall({
|
|
41
|
+
target: `${call.packageId}::blink::deposit`,
|
|
42
|
+
typeArguments: [call.coinType],
|
|
43
|
+
arguments: [
|
|
44
|
+
tx.object(call.escrowObject),
|
|
45
|
+
tx.pure.vector("u8", hexBytes(call.quoteHash)),
|
|
46
|
+
call.payment,
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
export const buildSuiDeposit = async (request) => {
|
|
51
|
+
const rpc = client(request.rpcUrl);
|
|
52
|
+
const [first, ...rest] = await collectCoins(rpc, request.owner, request.coinType, request.amount);
|
|
53
|
+
const tx = new Transaction();
|
|
54
|
+
tx.setSender(request.owner);
|
|
55
|
+
const primary = tx.object(first);
|
|
56
|
+
if (rest.length > 0) {
|
|
57
|
+
tx.mergeCoins(primary, rest.map((id) => tx.object(id)));
|
|
58
|
+
}
|
|
59
|
+
const [payment] = tx.splitCoins(primary, [request.amount]);
|
|
60
|
+
addSuiDepositCall(tx, {
|
|
61
|
+
packageId: request.packageId,
|
|
62
|
+
escrowObject: request.escrowObject,
|
|
63
|
+
coinType: request.coinType,
|
|
64
|
+
quoteHash: request.quoteHash,
|
|
65
|
+
payment,
|
|
66
|
+
});
|
|
67
|
+
return tx;
|
|
68
|
+
};
|
|
69
|
+
const SPONSORED_BUDGET_MARGIN_BPS = 12500n;
|
|
70
|
+
export const sponsorSuiTransaction = async (rpcUrl, transaction, sponsor, maxGasBudget) => {
|
|
71
|
+
const rpc = client(rpcUrl);
|
|
72
|
+
transaction.setGasOwner(sponsor);
|
|
73
|
+
transaction.setGasPayment([]);
|
|
74
|
+
transaction.setGasBudget(maxGasBudget);
|
|
75
|
+
const simulated = await rpc.core.simulateTransaction({
|
|
76
|
+
transaction: await transaction.build({ client: rpc }),
|
|
77
|
+
include: { effects: true },
|
|
78
|
+
});
|
|
79
|
+
if (simulated.$kind === "FailedTransaction") {
|
|
80
|
+
throw new Error(`sponsored deposit would fail: ${simulated.FailedTransaction.status.error?.message ?? "unknown"}`);
|
|
81
|
+
}
|
|
82
|
+
const { computationCost, storageCost } = simulated.Transaction.effects.gasUsed;
|
|
83
|
+
const needed = ((BigInt(computationCost) + BigInt(storageCost)) *
|
|
84
|
+
SPONSORED_BUDGET_MARGIN_BPS) /
|
|
85
|
+
10000n;
|
|
86
|
+
transaction.setGasBudget(needed < maxGasBudget ? needed : maxGasBudget);
|
|
87
|
+
return Transaction.from(await transaction.build({ client: rpc }));
|
|
88
|
+
};
|
|
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
|
+
}
|
|
180
|
+
});
|
|
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");
|
|
186
|
+
}
|
|
187
|
+
};
|
|
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
|
+
}
|
|
241
|
+
});
|
|
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,
|
|
249
|
+
});
|
|
250
|
+
const bridged = appendSwapFragment(tx, fragment, call.payment, coinOut);
|
|
251
|
+
tx.moveCall({
|
|
252
|
+
target: `${call.packageId}::blink::deposit_swap`,
|
|
253
|
+
typeArguments: [call.srcToken, call.bridgeToken],
|
|
254
|
+
arguments: [
|
|
255
|
+
tx.object(call.escrowObject),
|
|
256
|
+
tx.pure.vector("u8", hexBytes(call.quoteHash)),
|
|
257
|
+
bridged,
|
|
258
|
+
tx.pure.u64(call.amountIn),
|
|
259
|
+
tx.pure.u64(call.minBridgeOut),
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const isSuiCoinType = (coinType) => normalizeStructTag(coinType) === normalizeStructTag(SUI_COIN_TYPE);
|
|
264
|
+
const addPayment = async (tx, rpcUrl, owner, coinType, amount) => {
|
|
265
|
+
if (isSuiCoinType(coinType)) {
|
|
266
|
+
const [payment] = tx.splitCoins(tx.gas, [amount]);
|
|
267
|
+
return payment;
|
|
268
|
+
}
|
|
269
|
+
const [first, ...rest] = await collectCoins(client(rpcUrl), owner, coinType, amount);
|
|
270
|
+
const primary = tx.object(first);
|
|
271
|
+
if (rest.length > 0) {
|
|
272
|
+
tx.mergeCoins(primary, rest.map((id) => tx.object(id)));
|
|
273
|
+
}
|
|
274
|
+
const [payment] = tx.splitCoins(primary, [amount]);
|
|
275
|
+
return payment;
|
|
276
|
+
};
|
|
277
|
+
export const buildSuiSwapDeposit = async (request) => {
|
|
278
|
+
const tx = new Transaction();
|
|
279
|
+
tx.setSender(request.owner);
|
|
280
|
+
const payment = await addPayment(tx, request.rpcUrl, request.owner, request.srcToken, request.amountIn);
|
|
281
|
+
addSuiSwapDepositCall(tx, {
|
|
282
|
+
packageId: request.packageId,
|
|
283
|
+
escrowObject: request.escrowObject,
|
|
284
|
+
sender: request.owner,
|
|
285
|
+
srcToken: request.srcToken,
|
|
286
|
+
bridgeToken: request.bridgeToken,
|
|
287
|
+
amountIn: request.amountIn,
|
|
288
|
+
minBridgeOut: request.minBridgeOut,
|
|
289
|
+
quoteHash: request.quoteHash,
|
|
290
|
+
swap: request.swap,
|
|
291
|
+
payment,
|
|
292
|
+
});
|
|
293
|
+
return tx;
|
|
294
|
+
};
|
|
295
|
+
export const buildSuiVoucherRedemption = (request) => {
|
|
296
|
+
const tx = new Transaction();
|
|
297
|
+
tx.setSender(request.sender);
|
|
298
|
+
tx.moveCall({
|
|
299
|
+
target: `${request.packageId}::blink::redeem_voucher`,
|
|
300
|
+
typeArguments: [request.coinType],
|
|
301
|
+
arguments: [
|
|
302
|
+
tx.object(request.escrowObject),
|
|
303
|
+
tx.pure.vector("u8", hexBytes(request.id)),
|
|
304
|
+
tx.pure.address(request.referrer),
|
|
305
|
+
tx.pure.u64(request.amount),
|
|
306
|
+
tx.pure.vector("u8", hexBytes(request.signature)),
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
return tx;
|
|
310
|
+
};
|
package/dist/svm.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AddressLookupTableAccount, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
import type { SvmSwapInstructions } from "./gen/blink/v1/api_pb.js";
|
|
3
|
+
export declare const DEFAULT_SWAP_PROGRAMS: PublicKey[];
|
|
4
|
+
export declare const isNativeSvmToken: (address: string) => boolean;
|
|
5
|
+
export declare const svmBalance: (rpcUrl: string, token: string, owner: string) => Promise<bigint>;
|
|
6
|
+
export interface SvmDepositRequest {
|
|
7
|
+
rpcUrl: string;
|
|
8
|
+
programId: string;
|
|
9
|
+
owner: string;
|
|
10
|
+
token: string;
|
|
11
|
+
amount: bigint;
|
|
12
|
+
quoteHash: string;
|
|
13
|
+
feePayer?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const resolveSwapPrograms: (chain: string, swap: SvmSwapInstructions, expectedSigner: string | undefined) => PublicKey[];
|
|
16
|
+
export interface SvmSwapDepositRequest {
|
|
17
|
+
rpcUrl: string;
|
|
18
|
+
chain: string;
|
|
19
|
+
programId: string;
|
|
20
|
+
owner: string;
|
|
21
|
+
srcToken: string;
|
|
22
|
+
bridgeToken: string;
|
|
23
|
+
amount: bigint;
|
|
24
|
+
minBridgeOut: bigint;
|
|
25
|
+
quoteHash: string;
|
|
26
|
+
swap: SvmSwapInstructions;
|
|
27
|
+
swapProgramSigner?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SvmDepositInstructions {
|
|
30
|
+
instructions: TransactionInstruction[];
|
|
31
|
+
lookupTables: AddressLookupTableAccount[];
|
|
32
|
+
}
|
|
33
|
+
export declare const buildSvmSwapDepositInstructions: (request: SvmSwapDepositRequest) => Promise<SvmDepositInstructions>;
|
|
34
|
+
export declare const buildSvmSwapDeposit: (request: SvmSwapDepositRequest) => Promise<Uint8Array>;
|
|
35
|
+
export declare const buildSvmDepositInstructions: (request: SvmDepositRequest) => Promise<TransactionInstruction[]>;
|
|
36
|
+
export declare const buildSvmDeposit: (request: SvmDepositRequest) => Promise<Uint8Array>;
|