Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constrain token payment by auth entries #31

Merged
merged 7 commits into from
Feb 8, 2024
6 changes: 6 additions & 0 deletions src/helper/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const ERROR = {
ACCOUNT_NOT_SOURCE:
"Transfer contains authorization entry for a different account",
AUTH_SUB_INVOCATIONS:
"Transfer authorizes sub-invocations to another contract",
};
2 changes: 1 addition & 1 deletion src/helper/horizon-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export const submitTransaction = async (
error: null,
};
} catch (e: any) {
if (e.response.status === 504) {
if (e.response?.status === 504) {
// in case of 504, keep retrying this tx until submission succeeds or we get a different error
// https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/timeout
// https://developers.stellar.org/docs/encyclopedia/error-handling
Expand Down
30 changes: 28 additions & 2 deletions src/route/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import {
Transaction,
TransactionBuilder,
XdrLargeInt,
xdr,
} from "stellar-sdk";
import { buildTransfer, simulateTx } from "../helper/soroban-rpc";
import { ERROR } from "../helper/error";

const API_VERSION = "v1";

Expand Down Expand Up @@ -545,17 +547,41 @@ export async function initApiServer(
const simulationResponse = (await server.simulateTransaction(
tx
)) as SorobanRpc.Api.SimulateTransactionSuccessResponse;

const preparedTransaction = SorobanRpc.assembleTransaction(
tx,
simulationResponse
);

const built = preparedTransaction.build();
switch (built.operations[0].type) {
case "invokeHostFunction": {
const sorobanOp = built
.operations[0] as Operation.InvokeHostFunction;
const auths = sorobanOp.auth || [];

for (const auth of auths) {
if (
auth.credentials().switch() !==
xdr.SorobanCredentialsType.sorobanCredentialsSourceAccount()
) {
throw new Error(ERROR.ACCOUNT_NOT_SOURCE);
}

if (auth.rootInvocation().subInvocations().length) {
throw new Error(ERROR.AUTH_SUB_INVOCATIONS);
}
}
}
}

const data = {
simulationResponse,
preparedTransaction: preparedTransaction.build().toXDR(),
preparedTransaction: built.toXDR(),
};
reply.code(200).send(data);
} catch (error) {
reply.code(400).send(JSON.stringify(error));
reply.code(400).send(error);
}
},
});
Expand Down
18 changes: 10 additions & 8 deletions src/service/mercury/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ enum NETWORK_URLS {
}

const ERROR_MESSAGES = {
JWT_EXPIRED: "jwt expired",
JWT_EXPIRED: "1_kJdMBB7ytvgRIqF1clh2iz2iI",
};

function getGraphQlError(error?: CombinedError) {
Expand Down Expand Up @@ -179,7 +179,7 @@ export class MercuryClient {
} catch (error: unknown) {
// renew and retry 0n 401, otherwise throw the error back up to the caller
if (error instanceof Error) {
if (error.message === ERROR_MESSAGES.JWT_EXPIRED) {
if (error.message.includes(ERROR_MESSAGES.JWT_EXPIRED)) {
await this.renewMercuryToken();
this.logger.info("renewed expired jwt");
return await method();
Expand Down Expand Up @@ -518,9 +518,10 @@ export class MercuryClient {
) => {
const balances = [];
const balanceMap = {} as Record<string, any>;
try {
const server = await getServer(network, customSorobanRpcUrl);
for (const id of contractIds) {

const server = await getServer(network, customSorobanRpcUrl);
for (const id of contractIds) {
try {
const builder = await getTxBuilder(pubKey, network, server);
const params = [new Address(pubKey).toScVal()];
const balance = await getTokenBalance(id, params, server, builder);
Expand All @@ -530,11 +531,12 @@ export class MercuryClient {
balance,
...tokenDetails,
});
} catch (error) {
this.logger.error(error);
continue;
}
} catch (error) {
this.logger.error(error);
return balanceMap;
}

for (const balance of balances) {
balanceMap[`${balance.symbol}:${balance.id}`] = {
token: {
Expand Down
Loading