@helium/blockchain-api 0.1.1 → 0.1.3
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 +24 -2
- package/package.json +31 -31
- package/types/README.md +24 -2
- package/types/generated/index.d.mts +2481 -0
- package/types/generated/index.d.ts +2481 -0
- package/types/generated/index.js +723 -0
- package/types/generated/index.mjs +614 -0
- package/types/index.ts +11 -8
- package/src/server/api/errors.ts +0 -152
- package/src/server/api/index.ts +0 -40
- package/src/server/api/procedures.ts +0 -144
- package/src/server/api/routers/fiat/router.ts +0 -709
- package/src/server/api/routers/fiat/schemas.ts +0 -157
- package/src/server/api/routers/health/router.ts +0 -41
- package/src/server/api/routers/hotspots/procedures/claimRewards.ts +0 -258
- package/src/server/api/routers/hotspots/procedures/createSplit.ts +0 -253
- package/src/server/api/routers/hotspots/procedures/deleteSplit.ts +0 -156
- package/src/server/api/routers/hotspots/procedures/getHotspots.ts +0 -31
- package/src/server/api/routers/hotspots/procedures/getPendingRewards.ts +0 -44
- package/src/server/api/routers/hotspots/procedures/getSplit.ts +0 -88
- package/src/server/api/routers/hotspots/procedures/transferHotspot.ts +0 -204
- package/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts +0 -201
- package/src/server/api/routers/hotspots/router.ts +0 -30
- package/src/server/api/routers/hotspots/schemas.ts +0 -182
- package/src/server/api/routers/swap/procedures/getInstructions.ts +0 -152
- package/src/server/api/routers/swap/procedures/getQuote.ts +0 -53
- package/src/server/api/routers/swap/procedures/getTokens.ts +0 -88
- package/src/server/api/routers/swap/router.ts +0 -15
- package/src/server/api/routers/swap/schemas.ts +0 -96
- package/src/server/api/routers/tokens/procedures/createHntAccount.ts +0 -87
- package/src/server/api/routers/tokens/procedures/getBalances.ts +0 -27
- package/src/server/api/routers/tokens/procedures/transfer.ts +0 -159
- package/src/server/api/routers/tokens/router.ts +0 -15
- package/src/server/api/routers/tokens/schemas.ts +0 -80
- package/src/server/api/routers/transactions/procedures/get.ts +0 -46
- package/src/server/api/routers/transactions/procedures/getByPayer.ts +0 -111
- package/src/server/api/routers/transactions/procedures/getByPayerAndTag.ts +0 -119
- package/src/server/api/routers/transactions/procedures/resubmit.ts +0 -68
- package/src/server/api/routers/transactions/procedures/submit.ts +0 -216
- package/src/server/api/routers/transactions/router.ts +0 -21
- package/src/server/api/routers/transactions/schemas.ts +0 -119
- package/src/server/api/routers/webhooks/router.ts +0 -75
- package/src/server/api/routers/welcomePacks/procedures/claim.ts +0 -157
- package/src/server/api/routers/welcomePacks/procedures/create.ts +0 -247
- package/src/server/api/routers/welcomePacks/procedures/deletePack.ts +0 -118
- package/src/server/api/routers/welcomePacks/procedures/get.ts +0 -36
- package/src/server/api/routers/welcomePacks/procedures/getByAddress.ts +0 -26
- package/src/server/api/routers/welcomePacks/procedures/invite.ts +0 -44
- package/src/server/api/routers/welcomePacks/procedures/list.ts +0 -27
- package/src/server/api/routers/welcomePacks/router.ts +0 -27
- package/src/server/api/routers/welcomePacks/schemas.ts +0 -135
- package/src/server/api/schemas.ts +0 -281
|
@@ -1,709 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { ORPCError } from "@orpc/server";
|
|
3
|
-
import { v4 as uuidv4 } from "uuid";
|
|
4
|
-
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
5
|
-
import {
|
|
6
|
-
createAssociatedTokenAccountIdempotentInstruction,
|
|
7
|
-
createTransferCheckedInstruction,
|
|
8
|
-
getAssociatedTokenAddressSync,
|
|
9
|
-
} from "@solana/spl-token";
|
|
10
|
-
import {
|
|
11
|
-
populateMissingDraftInfo,
|
|
12
|
-
toVersionedTx,
|
|
13
|
-
HNT_MINT,
|
|
14
|
-
withPriorityFees,
|
|
15
|
-
} from "@helium/spl-utils";
|
|
16
|
-
import { Op } from "sequelize";
|
|
17
|
-
|
|
18
|
-
import { protectedProcedure, publicProcedure } from "../../procedures";
|
|
19
|
-
import {
|
|
20
|
-
InitKycInputSchema,
|
|
21
|
-
CreateBankAccountInputSchema,
|
|
22
|
-
GetBankAccountInputSchema,
|
|
23
|
-
DeleteBankAccountInputSchema,
|
|
24
|
-
GetSendQuoteInputSchema,
|
|
25
|
-
SendFundsInputSchema,
|
|
26
|
-
GetTransferInputSchema,
|
|
27
|
-
UpdateTransferInputSchema,
|
|
28
|
-
KycStatusOutputSchema,
|
|
29
|
-
FeesOutputSchema,
|
|
30
|
-
BankAccountListOutputSchema,
|
|
31
|
-
BankAccountSchema,
|
|
32
|
-
DeleteBankAccountOutputSchema,
|
|
33
|
-
QuoteOutputSchema,
|
|
34
|
-
SendFundsOutputSchema,
|
|
35
|
-
UpdateTransferOutputSchema,
|
|
36
|
-
} from "./schemas";
|
|
37
|
-
import { env } from "@/lib/env";
|
|
38
|
-
import { privy } from "@/lib/privy";
|
|
39
|
-
import { BridgeUser } from "@/lib/models/bridge-user";
|
|
40
|
-
import { BankAccount } from "@/lib/models/bank-account";
|
|
41
|
-
import { BridgeTransfer } from "@/lib/models/bridge-transfer";
|
|
42
|
-
import { TOKEN_MINTS } from "@/lib/constants/tokens";
|
|
43
|
-
import {
|
|
44
|
-
generateTransactionTag,
|
|
45
|
-
TRANSACTION_TYPES,
|
|
46
|
-
} from "@/lib/utils/transaction-tags";
|
|
47
|
-
|
|
48
|
-
// Helper functions
|
|
49
|
-
const usdToUsdc = (usdAmount: string) => {
|
|
50
|
-
if (!usdAmount || isNaN(parseFloat(usdAmount))) return null;
|
|
51
|
-
return (parseFloat(usdAmount) * Math.pow(10, 6)).toString();
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const usdcToUsd = (usdcAmount: string) => {
|
|
55
|
-
return parseFloat(usdcAmount) / Math.pow(10, 6);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const usdCeil = (amount: number) => {
|
|
59
|
-
return Math.ceil(amount * Math.pow(10, 2)) / Math.pow(10, 2);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// ============================================================================
|
|
63
|
-
// Procedures
|
|
64
|
-
// ============================================================================
|
|
65
|
-
|
|
66
|
-
const getKycStatus = protectedProcedure
|
|
67
|
-
.route({ method: "GET", path: "/fiat/kyc" })
|
|
68
|
-
.output(KycStatusOutputSchema)
|
|
69
|
-
.errors({
|
|
70
|
-
EMAIL_NOT_LINKED: { message: "Email not linked" },
|
|
71
|
-
})
|
|
72
|
-
.handler(async ({ context, errors }) => {
|
|
73
|
-
const privyUser = context.session.user;
|
|
74
|
-
|
|
75
|
-
if (!privyUser.email) {
|
|
76
|
-
throw errors.EMAIL_NOT_LINKED({ message: "Email not linked" });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const bridgeUser = await BridgeUser.findOne({
|
|
80
|
-
where: { privyUserId: privyUser.id },
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
if (bridgeUser) {
|
|
84
|
-
return {
|
|
85
|
-
kycStatus: bridgeUser.kycStatus,
|
|
86
|
-
tosStatus: bridgeUser.tosStatus,
|
|
87
|
-
tosLink: bridgeUser.tosLink,
|
|
88
|
-
kycLink: bridgeUser.kycLink,
|
|
89
|
-
kycLinkId: bridgeUser.kycLinkId,
|
|
90
|
-
accountType: bridgeUser.accountType,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
kycStatus: "not_started",
|
|
96
|
-
tosStatus: "pending",
|
|
97
|
-
tosLink: null,
|
|
98
|
-
kycLink: null,
|
|
99
|
-
kycLinkId: null,
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const initKyc = protectedProcedure
|
|
104
|
-
.route({ method: "POST", path: "/fiat/kyc" })
|
|
105
|
-
.input(InitKycInputSchema)
|
|
106
|
-
.output(KycStatusOutputSchema)
|
|
107
|
-
.errors({
|
|
108
|
-
EMAIL_NOT_LINKED: { message: "Email not linked" },
|
|
109
|
-
BRIDGE_ERROR: { message: "Failed to create Bridge KYC link" },
|
|
110
|
-
})
|
|
111
|
-
.handler(async ({ input, context, errors }) => {
|
|
112
|
-
const { type } = input;
|
|
113
|
-
const privyUser = context.session.user;
|
|
114
|
-
|
|
115
|
-
if (!privyUser.email) {
|
|
116
|
-
throw errors.EMAIL_NOT_LINKED({ message: "Email not linked" });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let bridgeUser = await BridgeUser.findOne({
|
|
120
|
-
where: { privyUserId: privyUser.id },
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (bridgeUser && !bridgeUser.accountType && type) {
|
|
124
|
-
bridgeUser.accountType = type;
|
|
125
|
-
await bridgeUser.save();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
bridgeUser?.kycStatus === "approved" &&
|
|
130
|
-
bridgeUser?.tosStatus === "approved"
|
|
131
|
-
) {
|
|
132
|
-
return {
|
|
133
|
-
kycStatus: bridgeUser.kycStatus,
|
|
134
|
-
tosStatus: bridgeUser.tosStatus,
|
|
135
|
-
tosLink: bridgeUser.tosLink,
|
|
136
|
-
kycLink: bridgeUser.kycLink,
|
|
137
|
-
kycLinkId: bridgeUser.kycLinkId,
|
|
138
|
-
accountType: bridgeUser.accountType,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let rejectionReasons: string[] = [];
|
|
143
|
-
|
|
144
|
-
if (!bridgeUser) {
|
|
145
|
-
const response = await fetch(`${env.BRIDGE_API_URL}/kyc_links`, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
headers: {
|
|
148
|
-
"Content-Type": "application/json",
|
|
149
|
-
"Api-Key": env.BRIDGE_API_KEY,
|
|
150
|
-
"Idempotency-Key": uuidv4(),
|
|
151
|
-
},
|
|
152
|
-
body: JSON.stringify({
|
|
153
|
-
email: privyUser.email.address,
|
|
154
|
-
type: type || "individual",
|
|
155
|
-
redirect_uri: `${
|
|
156
|
-
process.env.NEXT_PUBLIC_APP_URL
|
|
157
|
-
}/withdraw?step=5&type=${type || "individual"}`,
|
|
158
|
-
}),
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
let data = await response.json();
|
|
162
|
-
const existsAlready = data.existing_kyc_link;
|
|
163
|
-
if (existsAlready) {
|
|
164
|
-
data = data.existing_kyc_link;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!response.ok && !existsAlready) {
|
|
168
|
-
console.error("Failed to create Bridge KYC link", data);
|
|
169
|
-
throw errors.BRIDGE_ERROR({
|
|
170
|
-
message: "Failed to create Bridge KYC link",
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const [newBridgeUser] = await BridgeUser.findOrCreate({
|
|
175
|
-
where: { privyUserId: privyUser.id },
|
|
176
|
-
defaults: {
|
|
177
|
-
privyUserId: privyUser.id,
|
|
178
|
-
kycLinkId: data.id,
|
|
179
|
-
kycStatus: data.kyc_status,
|
|
180
|
-
tosStatus: data.tos_status,
|
|
181
|
-
tosLink: data.tos_link,
|
|
182
|
-
kycLink: data.kyc_link,
|
|
183
|
-
bridgeCustomerId: data.customer_id,
|
|
184
|
-
accountType: type || "individual",
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
bridgeUser = newBridgeUser;
|
|
189
|
-
} else {
|
|
190
|
-
const response = await fetch(
|
|
191
|
-
`${env.BRIDGE_API_URL}/kyc_links/${bridgeUser.kycLinkId}`,
|
|
192
|
-
{
|
|
193
|
-
method: "GET",
|
|
194
|
-
headers: {
|
|
195
|
-
"Content-Type": "application/json",
|
|
196
|
-
"Api-Key": env.BRIDGE_API_KEY,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
if (!response.ok) {
|
|
202
|
-
throw errors.BRIDGE_ERROR({ message: "Failed to get Bridge KYC link" });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const data = await response.json();
|
|
206
|
-
if (process.env.BRIDGE_API_URL?.includes("sandbox")) {
|
|
207
|
-
bridgeUser.tosStatus = "approved";
|
|
208
|
-
} else {
|
|
209
|
-
bridgeUser.tosStatus = data.tos_status;
|
|
210
|
-
}
|
|
211
|
-
bridgeUser.kycStatus = data.kyc_status;
|
|
212
|
-
bridgeUser.tosLink = data.tos_link;
|
|
213
|
-
bridgeUser.kycLink = data.kyc_link;
|
|
214
|
-
await bridgeUser.save();
|
|
215
|
-
rejectionReasons = (data.rejection_reasons || []).map(
|
|
216
|
-
(r: { reason: string }) => r.reason,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
kycStatus: bridgeUser.kycStatus,
|
|
222
|
-
tosStatus: bridgeUser.tosStatus,
|
|
223
|
-
tosLink: bridgeUser.tosLink,
|
|
224
|
-
kycLink: bridgeUser.kycLink,
|
|
225
|
-
kycLinkId: bridgeUser.kycLinkId,
|
|
226
|
-
accountType: bridgeUser.accountType,
|
|
227
|
-
rejectionReasons,
|
|
228
|
-
};
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const getFees = publicProcedure
|
|
232
|
-
.route({ method: "GET", path: "/fiat/fees" })
|
|
233
|
-
.output(FeesOutputSchema)
|
|
234
|
-
.handler(async () => {
|
|
235
|
-
const developerFee = process.env.BRIDGE_DEVELOPER_FEE || "0.50";
|
|
236
|
-
const developerFeePercentage =
|
|
237
|
-
process.env.BRIDGE_DEVELOPER_FEE_PERCENTAGE || "0.5";
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
developer_fee: developerFee,
|
|
241
|
-
developer_fee_percent: parseFloat(developerFeePercentage),
|
|
242
|
-
};
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const listBankAccounts = protectedProcedure
|
|
246
|
-
.route({ method: "GET", path: "/fiat/bank-accounts" })
|
|
247
|
-
.output(BankAccountListOutputSchema)
|
|
248
|
-
.errors({
|
|
249
|
-
NO_CUSTOMER: { message: "Bridge customer ID not found" },
|
|
250
|
-
})
|
|
251
|
-
.handler(async ({ context, errors }) => {
|
|
252
|
-
const privyUser = context.session.user;
|
|
253
|
-
|
|
254
|
-
const bridgeUser = await BridgeUser.findOne({
|
|
255
|
-
where: { privyUserId: privyUser.id },
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
if (!bridgeUser?.bridgeCustomerId) {
|
|
259
|
-
throw errors.NO_CUSTOMER({ message: "Bridge customer ID not found" });
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const bankAccounts = await BankAccount.findAll({
|
|
263
|
-
where: { bridgeUserId: bridgeUser.id },
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
return bankAccounts.map((account) => account.toJSON());
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const createBankAccount = protectedProcedure
|
|
270
|
-
.route({ method: "POST", path: "/fiat/bank-accounts" })
|
|
271
|
-
.input(CreateBankAccountInputSchema)
|
|
272
|
-
.output(BankAccountSchema)
|
|
273
|
-
.errors({
|
|
274
|
-
NO_CUSTOMER: { message: "Bridge customer ID not found" },
|
|
275
|
-
BRIDGE_ERROR: { message: "Failed to add bank account" },
|
|
276
|
-
})
|
|
277
|
-
.handler(async ({ input, context, errors }) => {
|
|
278
|
-
const privyUser = context.session.user;
|
|
279
|
-
|
|
280
|
-
const bridgeUser = await BridgeUser.findOne({
|
|
281
|
-
where: { privyUserId: privyUser.id },
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
if (!bridgeUser?.bridgeCustomerId || !bridgeUser.id) {
|
|
285
|
-
throw errors.NO_CUSTOMER({ message: "Bridge customer ID not found" });
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const {
|
|
289
|
-
currency,
|
|
290
|
-
account_type,
|
|
291
|
-
bank_name,
|
|
292
|
-
account_name,
|
|
293
|
-
first_name,
|
|
294
|
-
last_name,
|
|
295
|
-
account_owner_name,
|
|
296
|
-
business_name,
|
|
297
|
-
account,
|
|
298
|
-
address,
|
|
299
|
-
} = input;
|
|
300
|
-
|
|
301
|
-
const bankAccountResponse = await fetch(
|
|
302
|
-
`${env.BRIDGE_API_URL}/customers/${bridgeUser.bridgeCustomerId}/external_accounts`,
|
|
303
|
-
{
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: {
|
|
306
|
-
"Content-Type": "application/json",
|
|
307
|
-
"Api-Key": env.BRIDGE_API_KEY,
|
|
308
|
-
"Idempotency-Key": uuidv4(),
|
|
309
|
-
},
|
|
310
|
-
body: JSON.stringify({
|
|
311
|
-
currency,
|
|
312
|
-
account_type,
|
|
313
|
-
bank_name,
|
|
314
|
-
account_name,
|
|
315
|
-
first_name,
|
|
316
|
-
last_name,
|
|
317
|
-
account_owner_type: bridgeUser.accountType,
|
|
318
|
-
account_owner_name,
|
|
319
|
-
...(business_name && { business_name }),
|
|
320
|
-
account: {
|
|
321
|
-
routing_number: account.routing_number,
|
|
322
|
-
account_number: account.account_number,
|
|
323
|
-
checking_or_savings: account.checking_or_savings,
|
|
324
|
-
},
|
|
325
|
-
address: {
|
|
326
|
-
street_line_1: address.street_line_1,
|
|
327
|
-
country: address.country,
|
|
328
|
-
state: address.state,
|
|
329
|
-
city: address.city,
|
|
330
|
-
postal_code: address.postal_code,
|
|
331
|
-
},
|
|
332
|
-
}),
|
|
333
|
-
},
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
if (!bankAccountResponse.ok) {
|
|
337
|
-
const errorData = await bankAccountResponse.json();
|
|
338
|
-
console.error("Failed to add bank account", errorData);
|
|
339
|
-
throw errors.BRIDGE_ERROR({
|
|
340
|
-
message: errorData.message || "Failed to add bank account",
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const bankAccountData = await bankAccountResponse.json();
|
|
345
|
-
|
|
346
|
-
const bankAccount = await BankAccount.create({
|
|
347
|
-
bridgeUserId: bridgeUser.id,
|
|
348
|
-
bridgeExternalAccountId: bankAccountData.id,
|
|
349
|
-
accountName: account_name,
|
|
350
|
-
bankName: bank_name,
|
|
351
|
-
lastFourDigits: account.account_number.slice(-4),
|
|
352
|
-
routingNumber: account.routing_number,
|
|
353
|
-
accountType: account.checking_or_savings,
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
return bankAccount.toJSON();
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const deleteBankAccount = protectedProcedure
|
|
360
|
-
.route({ method: "DELETE", path: "/fiat/bank-accounts/{id}" })
|
|
361
|
-
.input(DeleteBankAccountInputSchema)
|
|
362
|
-
.output(DeleteBankAccountOutputSchema)
|
|
363
|
-
.errors({
|
|
364
|
-
NO_CUSTOMER: { message: "Bridge customer ID not found" },
|
|
365
|
-
BRIDGE_ERROR: { message: "Failed to delete bank account" },
|
|
366
|
-
})
|
|
367
|
-
.handler(async ({ input, context, errors }) => {
|
|
368
|
-
const { id } = input;
|
|
369
|
-
const privyUser = context.session.user;
|
|
370
|
-
|
|
371
|
-
const bridgeUser = await BridgeUser.findOne({
|
|
372
|
-
where: { privyUserId: privyUser.id },
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
if (!bridgeUser?.bridgeCustomerId) {
|
|
376
|
-
throw errors.NO_CUSTOMER({ message: "Bridge customer ID not found" });
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const bankAccount = await BankAccount.findOne({
|
|
380
|
-
where: { bridgeExternalAccountId: id },
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
const response = await fetch(
|
|
384
|
-
`${env.BRIDGE_API_URL}/customers/${bridgeUser.bridgeCustomerId}/external_accounts/${id}`,
|
|
385
|
-
{
|
|
386
|
-
method: "DELETE",
|
|
387
|
-
headers: {
|
|
388
|
-
"Content-Type": "application/json",
|
|
389
|
-
"Api-Key": env.BRIDGE_API_KEY,
|
|
390
|
-
},
|
|
391
|
-
},
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
if (!response.ok) {
|
|
395
|
-
const errorData = await response.json();
|
|
396
|
-
console.error("Failed to delete bank account", errorData);
|
|
397
|
-
throw errors.BRIDGE_ERROR({
|
|
398
|
-
message: errorData.message || "Failed to delete bank account",
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
await bankAccount?.destroy();
|
|
403
|
-
|
|
404
|
-
return { success: true };
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const getSendQuote = publicProcedure
|
|
408
|
-
.route({ method: "GET", path: "/fiat/bank-accounts/{id}/send" })
|
|
409
|
-
.input(GetSendQuoteInputSchema)
|
|
410
|
-
.output(QuoteOutputSchema)
|
|
411
|
-
.errors({
|
|
412
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
413
|
-
JUPITER_ERROR: { message: "Failed to get quote from Jupiter" },
|
|
414
|
-
})
|
|
415
|
-
.handler(async ({ input, errors }) => {
|
|
416
|
-
const { usdAmount } = input;
|
|
417
|
-
|
|
418
|
-
const usdcAmount = usdToUsdc(usdAmount);
|
|
419
|
-
if (!usdcAmount) {
|
|
420
|
-
throw errors.BAD_REQUEST({ message: "Invalid USD amount" });
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const quoteResponse = await fetch(
|
|
424
|
-
`https://lite-api.jup.ag/swap/v1/quote?inputMint=${HNT_MINT.toBase58()}&outputMint=${
|
|
425
|
-
TOKEN_MINTS.USDC
|
|
426
|
-
}&swapMode=ExactOut&amount=${usdcAmount}&slippageBps=50`,
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
if (!quoteResponse.ok) {
|
|
430
|
-
throw errors.JUPITER_ERROR({
|
|
431
|
-
message: "Failed to get quote from Jupiter",
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return await quoteResponse.json();
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
const sendFunds = publicProcedure
|
|
439
|
-
.route({ method: "POST", path: "/fiat/bank-accounts/{id}/send" })
|
|
440
|
-
.input(SendFundsInputSchema)
|
|
441
|
-
.output(SendFundsOutputSchema)
|
|
442
|
-
.errors({
|
|
443
|
-
NOT_FOUND: { message: "Bank account not found" },
|
|
444
|
-
BRIDGE_ERROR: { message: "Failed to create Bridge transfer" },
|
|
445
|
-
JUPITER_ERROR: { message: "Failed to get swap instructions" },
|
|
446
|
-
})
|
|
447
|
-
.handler(async ({ input, errors }) => {
|
|
448
|
-
const { id, userAddress, quoteResponse } = input;
|
|
449
|
-
|
|
450
|
-
const bankAccount = await BankAccount.findByPk(id, {
|
|
451
|
-
include: [
|
|
452
|
-
{
|
|
453
|
-
as: "bridgeUser",
|
|
454
|
-
model: BridgeUser,
|
|
455
|
-
attributes: ["bridgeCustomerId"],
|
|
456
|
-
},
|
|
457
|
-
],
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
if (!bankAccount) {
|
|
461
|
-
throw errors.NOT_FOUND({ message: "Bank account not found" });
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Cancel any existing transfers
|
|
465
|
-
const existingTransfers = await BridgeTransfer.findAll({
|
|
466
|
-
where: {
|
|
467
|
-
bridgeUserId: bankAccount.bridgeUserId,
|
|
468
|
-
solanaSignature: { [Op.is]: null } as unknown as string,
|
|
469
|
-
state: { [Op.notIn]: ["cancelled", "failed"] },
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
for (const transfer of existingTransfers) {
|
|
474
|
-
try {
|
|
475
|
-
await fetch(
|
|
476
|
-
`https://api.bridge.xyz/v0/transfers/${transfer.bridgeTransferId}`,
|
|
477
|
-
{
|
|
478
|
-
method: "DELETE",
|
|
479
|
-
headers: {
|
|
480
|
-
"Api-Key": process.env.BRIDGE_API_KEY!,
|
|
481
|
-
"Content-Type": "application/json",
|
|
482
|
-
},
|
|
483
|
-
body: JSON.stringify({}),
|
|
484
|
-
},
|
|
485
|
-
);
|
|
486
|
-
await transfer.destroy();
|
|
487
|
-
} catch (e) {
|
|
488
|
-
console.error("Error cancelling transfer:", e);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Create transfer with Bridge
|
|
493
|
-
const fee = (
|
|
494
|
-
parseFloat(process.env.BRIDGE_DEVELOPER_FEE || "0.50") +
|
|
495
|
-
usdCeil(
|
|
496
|
-
(parseFloat(process.env.BRIDGE_DEVELOPER_FEE_PERCENTAGE || "0.5") /
|
|
497
|
-
100) *
|
|
498
|
-
usdcToUsd(quoteResponse.outAmount),
|
|
499
|
-
)
|
|
500
|
-
).toString();
|
|
501
|
-
|
|
502
|
-
const bridgeTransferResponse = await fetch(
|
|
503
|
-
"https://api.bridge.xyz/v0/transfers",
|
|
504
|
-
{
|
|
505
|
-
method: "POST",
|
|
506
|
-
headers: {
|
|
507
|
-
"Api-Key": process.env.BRIDGE_API_KEY!,
|
|
508
|
-
"Content-Type": "application/json",
|
|
509
|
-
"Idempotency-Key": uuidv4(),
|
|
510
|
-
},
|
|
511
|
-
body: JSON.stringify({
|
|
512
|
-
on_behalf_of: bankAccount.bridgeUser!.bridgeCustomerId,
|
|
513
|
-
amount: usdcToUsd(quoteResponse.outAmount).toString(),
|
|
514
|
-
developer_fee: fee,
|
|
515
|
-
source: {
|
|
516
|
-
payment_rail: "solana",
|
|
517
|
-
currency: "usdc",
|
|
518
|
-
from_address: userAddress,
|
|
519
|
-
},
|
|
520
|
-
destination: {
|
|
521
|
-
payment_rail: "ach",
|
|
522
|
-
currency: "usd",
|
|
523
|
-
external_account_id: bankAccount.bridgeExternalAccountId,
|
|
524
|
-
},
|
|
525
|
-
}),
|
|
526
|
-
},
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
if (!bridgeTransferResponse.ok) {
|
|
530
|
-
const error = await bridgeTransferResponse.json();
|
|
531
|
-
throw errors.BRIDGE_ERROR({
|
|
532
|
-
message: error.message || "Failed to create Bridge transfer",
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const bridgeTransfer = await bridgeTransferResponse.json();
|
|
537
|
-
const destination = bridgeTransfer.source_deposit_instructions.to_address;
|
|
538
|
-
|
|
539
|
-
const ata = getAssociatedTokenAddressSync(
|
|
540
|
-
new PublicKey(TOKEN_MINTS.USDC),
|
|
541
|
-
new PublicKey(destination),
|
|
542
|
-
true,
|
|
543
|
-
);
|
|
544
|
-
|
|
545
|
-
await BridgeTransfer.create({
|
|
546
|
-
bridgeTransferId: bridgeTransfer.id,
|
|
547
|
-
bridgeUserId: bankAccount.bridgeUserId,
|
|
548
|
-
bankAccountId: bankAccount.id!,
|
|
549
|
-
amount: quoteResponse.outAmount,
|
|
550
|
-
state: bridgeTransfer.state,
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Get swap instructions from Jupiter
|
|
554
|
-
const myUsdcAta = getAssociatedTokenAddressSync(
|
|
555
|
-
new PublicKey(TOKEN_MINTS.USDC),
|
|
556
|
-
new PublicKey(userAddress),
|
|
557
|
-
true,
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
const instructionsResponse = await fetch(
|
|
561
|
-
"https://lite-api.jup.ag/swap/v1/swap-instructions",
|
|
562
|
-
{
|
|
563
|
-
method: "POST",
|
|
564
|
-
headers: { "Content-Type": "application/json" },
|
|
565
|
-
body: JSON.stringify({
|
|
566
|
-
quoteResponse,
|
|
567
|
-
userPublicKey: userAddress,
|
|
568
|
-
destinationTokenAccount: myUsdcAta.toBase58(),
|
|
569
|
-
dynamicComputeUnitLimit: true,
|
|
570
|
-
prioritizationFeeLamports: {
|
|
571
|
-
priorityLevelWithMaxLamports: {
|
|
572
|
-
maxLamports: 1000000,
|
|
573
|
-
priorityLevel: "medium",
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
}),
|
|
577
|
-
},
|
|
578
|
-
);
|
|
579
|
-
|
|
580
|
-
const instructions = await instructionsResponse.json();
|
|
581
|
-
if (instructions.error) {
|
|
582
|
-
throw errors.JUPITER_ERROR({
|
|
583
|
-
message: "Failed to get swap instructions: " + instructions.error,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const deserializeInstruction = (instruction: {
|
|
588
|
-
programId: string;
|
|
589
|
-
accounts: { pubkey: string; isSigner: boolean; isWritable: boolean }[];
|
|
590
|
-
data: string;
|
|
591
|
-
}) => {
|
|
592
|
-
return new TransactionInstruction({
|
|
593
|
-
programId: new PublicKey(instruction.programId),
|
|
594
|
-
keys: instruction.accounts.map((key) => ({
|
|
595
|
-
pubkey: new PublicKey(key.pubkey),
|
|
596
|
-
isSigner: key.isSigner,
|
|
597
|
-
isWritable: key.isWritable,
|
|
598
|
-
})),
|
|
599
|
-
data: Buffer.from(instruction.data, "base64"),
|
|
600
|
-
});
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
const jupIxs = [
|
|
604
|
-
createAssociatedTokenAccountIdempotentInstruction(
|
|
605
|
-
new PublicKey(userAddress),
|
|
606
|
-
myUsdcAta,
|
|
607
|
-
new PublicKey(userAddress),
|
|
608
|
-
new PublicKey(TOKEN_MINTS.USDC),
|
|
609
|
-
),
|
|
610
|
-
deserializeInstruction(instructions.swapInstruction),
|
|
611
|
-
createAssociatedTokenAccountIdempotentInstruction(
|
|
612
|
-
new PublicKey(userAddress),
|
|
613
|
-
ata,
|
|
614
|
-
new PublicKey(destination),
|
|
615
|
-
new PublicKey(TOKEN_MINTS.USDC),
|
|
616
|
-
),
|
|
617
|
-
createTransferCheckedInstruction(
|
|
618
|
-
myUsdcAta,
|
|
619
|
-
new PublicKey(TOKEN_MINTS.USDC),
|
|
620
|
-
ata,
|
|
621
|
-
new PublicKey(userAddress),
|
|
622
|
-
BigInt(quoteResponse.outAmount),
|
|
623
|
-
6,
|
|
624
|
-
),
|
|
625
|
-
];
|
|
626
|
-
|
|
627
|
-
const draft = {
|
|
628
|
-
instructions: jupIxs,
|
|
629
|
-
feePayer: new PublicKey(userAddress),
|
|
630
|
-
addressLookupTableAddresses: instructions.addressLookupTableAddresses.map(
|
|
631
|
-
(address: string) => new PublicKey(address),
|
|
632
|
-
),
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
const connection = new Connection(process.env.SOLANA_RPC_URL!);
|
|
636
|
-
const tx = toVersionedTx(
|
|
637
|
-
await populateMissingDraftInfo(connection, {
|
|
638
|
-
...draft,
|
|
639
|
-
instructions: await withPriorityFees({ ...draft, connection }),
|
|
640
|
-
}),
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
const tag = generateTransactionTag({
|
|
644
|
-
type: TRANSACTION_TYPES.BANK_SEND,
|
|
645
|
-
bankAccountId: id,
|
|
646
|
-
userAddress,
|
|
647
|
-
amount: quoteResponse.outAmount,
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
return {
|
|
651
|
-
bridgeTransfer,
|
|
652
|
-
transactionData: {
|
|
653
|
-
transactions: [
|
|
654
|
-
{
|
|
655
|
-
serializedTransaction: Buffer.from(tx.serialize()).toString(
|
|
656
|
-
"base64",
|
|
657
|
-
),
|
|
658
|
-
metadata: {
|
|
659
|
-
type: "bank_send",
|
|
660
|
-
description: `Withdraw $${(
|
|
661
|
-
parseFloat(quoteResponse.outAmount) / 1e6
|
|
662
|
-
).toFixed(2)} worth of HNT to Bank Account`,
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
],
|
|
666
|
-
parallel: false,
|
|
667
|
-
tag,
|
|
668
|
-
},
|
|
669
|
-
};
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
const updateTransfer = publicProcedure
|
|
673
|
-
.route({ method: "PATCH", path: "/fiat/transfers/{id}" })
|
|
674
|
-
.input(UpdateTransferInputSchema)
|
|
675
|
-
.output(UpdateTransferOutputSchema)
|
|
676
|
-
.errors({
|
|
677
|
-
NOT_FOUND: { message: "Transfer not found" },
|
|
678
|
-
})
|
|
679
|
-
.handler(async ({ input, errors }) => {
|
|
680
|
-
const { id, solanaSignature } = input;
|
|
681
|
-
|
|
682
|
-
const transfer = await BridgeTransfer.findOne({
|
|
683
|
-
where: { bridgeTransferId: id },
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
if (!transfer) {
|
|
687
|
-
throw errors.NOT_FOUND({ message: "Transfer not found" });
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
await transfer.update({ solanaSignature });
|
|
691
|
-
|
|
692
|
-
return { success: true };
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
// ============================================================================
|
|
696
|
-
// Router Export
|
|
697
|
-
// ============================================================================
|
|
698
|
-
|
|
699
|
-
export const fiatRouter = {
|
|
700
|
-
getKycStatus,
|
|
701
|
-
initKyc,
|
|
702
|
-
getFees,
|
|
703
|
-
listBankAccounts,
|
|
704
|
-
createBankAccount,
|
|
705
|
-
deleteBankAccount,
|
|
706
|
-
getSendQuote,
|
|
707
|
-
sendFunds,
|
|
708
|
-
updateTransfer,
|
|
709
|
-
};
|