mirror of
https://github.com/alexgo-io/xverse-stacks-transaction-sponsor.git
synced 2026-01-12 22:43:48 +08:00
style: run prettier --write src
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"quoteProps": "as-needed",
|
"quoteProps": "as-needed",
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import {
|
import {
|
||||||
deserializeTransaction,
|
deserializeTransaction,
|
||||||
sponsorTransaction,
|
sponsorTransaction,
|
||||||
broadcastTransaction,
|
broadcastTransaction,
|
||||||
SponsoredAuthorization
|
SponsoredAuthorization,
|
||||||
} from "@stacks/transactions";
|
} from '@stacks/transactions';
|
||||||
import { bytesToHex } from "@stacks/common";
|
import { bytesToHex } from '@stacks/common';
|
||||||
import { StacksMainnet } from '@stacks/network';
|
import { StacksMainnet } from '@stacks/network';
|
||||||
import envVariables from '../../../config/config';
|
import envVariables from '../../../config/config';
|
||||||
import {
|
import { SponsorAccountsKey } from '../../constants';
|
||||||
SponsorAccountsKey,
|
|
||||||
} from '../../constants'
|
|
||||||
import {
|
import {
|
||||||
lockRandomSponsorAccount,
|
lockRandomSponsorAccount,
|
||||||
getAccountNonce,
|
getAccountNonce,
|
||||||
incrementAccountNonce,
|
incrementAccountNonce,
|
||||||
unlockSponsorAccount,
|
unlockSponsorAccount,
|
||||||
check
|
check,
|
||||||
} from '../../nonce';
|
} from '../../nonce';
|
||||||
import {
|
import { getAccountAddress } from '../../utils';
|
||||||
getAccountAddress
|
import { validateTransaction } from '../../validation';
|
||||||
} from '../../utils';
|
|
||||||
import {
|
|
||||||
validateTransaction
|
|
||||||
} from '../../validation';
|
|
||||||
|
|
||||||
let cache = require('../../cache');
|
let cache = require('../../cache');
|
||||||
|
|
||||||
@@ -32,14 +26,14 @@ export class Controller {
|
|||||||
try {
|
try {
|
||||||
const accounts = cache.instance().get(SponsorAccountsKey);
|
const accounts = cache.instance().get(SponsorAccountsKey);
|
||||||
const addresses = [];
|
const addresses = [];
|
||||||
accounts.forEach(account => {
|
accounts.forEach((account) => {
|
||||||
const address = getAccountAddress(account);
|
const address = getAccountAddress(account);
|
||||||
addresses.push(address);
|
addresses.push(address);
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
active: true,
|
active: true,
|
||||||
sponsor_addresses: addresses
|
sponsor_addresses: addresses,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -72,7 +66,7 @@ export class Controller {
|
|||||||
transaction: tx,
|
transaction: tx,
|
||||||
sponsorPrivateKey: account.stxPrivateKey,
|
sponsorPrivateKey: account.stxPrivateKey,
|
||||||
network,
|
network,
|
||||||
sponsorNonce: nonce
|
sponsorNonce: nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
// make sure fee doesn't exceed maximum
|
// make sure fee doesn't exceed maximum
|
||||||
@@ -93,7 +87,7 @@ export class Controller {
|
|||||||
incrementAccountNonce(account);
|
incrementAccountNonce(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({txid: result.txid, rawTx: bytesToHex(signedTx.serialize())});
|
return res.json({ txid: result.txid, rawTx: bytesToHex(signedTx.serialize()) });
|
||||||
} finally {
|
} finally {
|
||||||
unlockSponsorAccount(account);
|
unlockSponsorAccount(account);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Controller } from "./controller";
|
import { Controller } from './controller';
|
||||||
|
|
||||||
const express = require("express");
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const controller = new Controller();
|
const controller = new Controller();
|
||||||
|
|
||||||
router.get("/info", controller.info);
|
router.get('/info', controller.info);
|
||||||
router.post("/sponsor", controller.sponsor);
|
router.post('/sponsor', controller.sponsor);
|
||||||
|
|
||||||
export = router;
|
export = router;
|
||||||
|
|||||||
14
src/cache.ts
14
src/cache.ts
@@ -1,12 +1,12 @@
|
|||||||
let nodeCache = require('node-cache')
|
let nodeCache = require('node-cache');
|
||||||
let cache = null
|
let cache = null;
|
||||||
|
|
||||||
exports.start = function (done) {
|
exports.start = function (done) {
|
||||||
if (cache) return done()
|
if (cache) return done();
|
||||||
|
|
||||||
cache = new nodeCache()
|
cache = new nodeCache();
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.instance = function () {
|
exports.instance = function () {
|
||||||
return cache
|
return cache;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const SponsorAccountsKey = "SponsorAccounts";
|
export const SponsorAccountsKey = 'SponsorAccounts';
|
||||||
export const AddressNoncePrefix = "Nonce-";
|
export const AddressNoncePrefix = 'Nonce-';
|
||||||
export const AddressLockPrefix = "Lock-";
|
export const AddressLockPrefix = 'Lock-';
|
||||||
export const MaxLockAttempts = 10;
|
export const MaxLockAttempts = 10;
|
||||||
24
src/index.ts
24
src/index.ts
@@ -1,23 +1,23 @@
|
|||||||
import { json } from 'body-parser';
|
import { json } from 'body-parser';
|
||||||
import { errorHandler, wrongRouteHandler } from "./response";
|
import { errorHandler, wrongRouteHandler } from './response';
|
||||||
import { initializeSponsorWallet } from './initialization';
|
import { initializeSponsorWallet } from './initialization';
|
||||||
|
|
||||||
let cache = require('./cache')
|
let cache = require('./cache');
|
||||||
|
|
||||||
const express = require("express");
|
const express = require('express');
|
||||||
var cors = require('cors')
|
var cors = require('cors');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const v1 = require("./api/v1/router");
|
const v1 = require('./api/v1/router');
|
||||||
|
|
||||||
let port = 8080;
|
let port = 8080;
|
||||||
if (app.get("env") === "development") {
|
if (app.get('env') === 'development') {
|
||||||
port = 3100;
|
port = 3100;
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.start(function (err) {
|
cache.start(function (err) {
|
||||||
if (err) console.error(err)
|
if (err) console.error(err);
|
||||||
})
|
});
|
||||||
|
|
||||||
// initialize the sponsor wallets to the correct nonce
|
// initialize the sponsor wallets to the correct nonce
|
||||||
initializeSponsorWallet();
|
initializeSponsorWallet();
|
||||||
@@ -26,18 +26,18 @@ app.use(json());
|
|||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.send('Ok')
|
res.send('Ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use("/v1", v1);
|
app.use('/v1', v1);
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
app.use(wrongRouteHandler);
|
app.use(wrongRouteHandler);
|
||||||
|
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
console.log(
|
console.log(
|
||||||
`Stacks Transaction Sponsor Web Service started and listening at http://localhost:${port} in ${app.get(
|
`Stacks Transaction Sponsor Web Service started and listening at http://localhost:${port} in ${app.get(
|
||||||
"env"
|
'env',
|
||||||
)} mode`
|
)} mode`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import { StacksMainnet } from "@stacks/network";
|
import { StacksMainnet } from '@stacks/network';
|
||||||
import {
|
import { getAddressFromPrivateKey, TransactionVersion } from '@stacks/transactions';
|
||||||
getAddressFromPrivateKey,
|
import envVariables from '../config/config';
|
||||||
TransactionVersion
|
import axios from 'axios';
|
||||||
} from "@stacks/transactions";
|
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
||||||
import envVariables from "../config/config";
|
import { SponsorAccountsKey, AddressNoncePrefix } from './constants';
|
||||||
import axios from "axios";
|
|
||||||
import {
|
|
||||||
generateWallet,
|
|
||||||
generateNewAccount
|
|
||||||
} from "@stacks/wallet-sdk";
|
|
||||||
import {
|
|
||||||
SponsorAccountsKey,
|
|
||||||
AddressNoncePrefix
|
|
||||||
} from './constants'
|
|
||||||
|
|
||||||
let cache = require('./cache')
|
let cache = require('./cache');
|
||||||
|
|
||||||
export interface StxAddressDataResponse {
|
export interface StxAddressDataResponse {
|
||||||
balance: string;
|
balance: string;
|
||||||
@@ -44,7 +35,7 @@ export async function initializeSponsorWallet() {
|
|||||||
const result = cache.instance().set(SponsorAccountsKey, wallet.accounts);
|
const result = cache.instance().set(SponsorAccountsKey, wallet.accounts);
|
||||||
|
|
||||||
// get the correct next nonce for each addresses
|
// get the correct next nonce for each addresses
|
||||||
wallet.accounts.forEach(account => {
|
wallet.accounts.forEach((account) => {
|
||||||
const address = getAddressFromPrivateKey(account.stxPrivateKey, TransactionVersion.Mainnet);
|
const address = getAddressFromPrivateKey(account.stxPrivateKey, TransactionVersion.Mainnet);
|
||||||
setupAccountNonce(address);
|
setupAccountNonce(address);
|
||||||
});
|
});
|
||||||
@@ -68,12 +59,10 @@ export async function setupAccountNonce(address: string) {
|
|||||||
const nextNonce = nonce + pendingTransactionCount;
|
const nextNonce = nonce + pendingTransactionCount;
|
||||||
|
|
||||||
// cache correct next nonce
|
// cache correct next nonce
|
||||||
const result = cache.instance().set(AddressNoncePrefix+address, nextNonce);
|
const result = cache.instance().set(AddressNoncePrefix + address, nextNonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMempoolTransactions(
|
export async function getMempoolTransactions(stxAddress: string): Promise<number> {
|
||||||
stxAddress: string
|
|
||||||
): Promise<number> {
|
|
||||||
const network = new StacksMainnet();
|
const network = new StacksMainnet();
|
||||||
let apiUrl = `${network.coreApiUrl}/extended/v1/tx/mempool?address=${stxAddress}`;
|
let apiUrl = `${network.coreApiUrl}/extended/v1/tx/mempool?address=${stxAddress}`;
|
||||||
|
|
||||||
|
|||||||
27
src/nonce.ts
27
src/nonce.ts
@@ -1,24 +1,15 @@
|
|||||||
import {
|
import { SponsorAccountsKey, AddressNoncePrefix, AddressLockPrefix, MaxLockAttempts } from './constants';
|
||||||
SponsorAccountsKey,
|
|
||||||
AddressNoncePrefix,
|
|
||||||
AddressLockPrefix,
|
|
||||||
MaxLockAttempts
|
|
||||||
} from './constants'
|
|
||||||
|
|
||||||
import { Account } from '@stacks/wallet-sdk';
|
import { Account } from '@stacks/wallet-sdk';
|
||||||
|
|
||||||
import envVariables from "../config/config";
|
import envVariables from '../config/config';
|
||||||
|
|
||||||
import {
|
import { sleep, getRandomInt, getAccountAddress } from './utils';
|
||||||
sleep,
|
|
||||||
getRandomInt,
|
|
||||||
getAccountAddress
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
let cache = require('./cache');
|
let cache = require('./cache');
|
||||||
|
|
||||||
export function check(address: string): boolean {
|
export function check(address: string): boolean {
|
||||||
return cache.instance().get(AddressLockPrefix+address);
|
return cache.instance().get(AddressLockPrefix + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock address nonce
|
// lock address nonce
|
||||||
@@ -27,14 +18,14 @@ export function lock(address: string): boolean {
|
|||||||
if (lock === true) {
|
if (lock === true) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
cache.instance().set(AddressLockPrefix+address, true);
|
cache.instance().set(AddressLockPrefix + address, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlock address nonce
|
// unlock address nonce
|
||||||
export function unlock(address: string) {
|
export function unlock(address: string) {
|
||||||
cache.instance().set(AddressLockPrefix+address, false);
|
cache.instance().set(AddressLockPrefix + address, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomSponsorAccount(): Account {
|
export function getRandomSponsorAccount(): Account {
|
||||||
@@ -50,7 +41,7 @@ export async function lockRandomSponsorAccount(): Promise<Account> {
|
|||||||
var locked = false;
|
var locked = false;
|
||||||
var attempts = 0;
|
var attempts = 0;
|
||||||
|
|
||||||
while(!locked) {
|
while (!locked) {
|
||||||
const account = getRandomSponsorAccount();
|
const account = getRandomSponsorAccount();
|
||||||
const address = getAccountAddress(account);
|
const address = getAccountAddress(account);
|
||||||
locked = lock(address);
|
locked = lock(address);
|
||||||
@@ -73,12 +64,12 @@ export function unlockSponsorAccount(account: Account) {
|
|||||||
|
|
||||||
export function getAccountNonce(account: Account) {
|
export function getAccountNonce(account: Account) {
|
||||||
const address = getAccountAddress(account);
|
const address = getAccountAddress(account);
|
||||||
const nonce = cache.instance().get(AddressNoncePrefix+address);
|
const nonce = cache.instance().get(AddressNoncePrefix + address);
|
||||||
return nonce;
|
return nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function incrementAccountNonce(account: Account) {
|
export function incrementAccountNonce(account: Account) {
|
||||||
const address = getAccountAddress(account);
|
const address = getAccountAddress(account);
|
||||||
const currentNonce = getAccountNonce(account);
|
const currentNonce = getAccountNonce(account);
|
||||||
return cache.instance().set(AddressNoncePrefix+address, currentNonce + 1);
|
return cache.instance().set(AddressNoncePrefix + address, currentNonce + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ export function errorHandler(err, req, res, next) {
|
|||||||
err.statusCode = err.statusCode || 400;
|
err.statusCode = err.statusCode || 400;
|
||||||
res.status(err.statusCode).json({
|
res.status(err.statusCode).json({
|
||||||
status: err.statusCode,
|
status: err.statusCode,
|
||||||
message: err.message
|
message: err.message,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrongRouteHandler(req, res, next) {
|
export function wrongRouteHandler(req, res, next) {
|
||||||
const statusCode = 404;
|
const statusCode = 404;
|
||||||
res.status(statusCode).json({
|
res.status(statusCode).json({
|
||||||
status: statusCode,
|
status: statusCode,
|
||||||
message: "Route not found. Request failed with status code 404 "
|
message: 'Route not found. Request failed with status code 404 ',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
10
src/utils.ts
10
src/utils.ts
@@ -1,8 +1,5 @@
|
|||||||
import { Account } from '@stacks/wallet-sdk';
|
import { Account } from '@stacks/wallet-sdk';
|
||||||
import {
|
import { getAddressFromPrivateKey, TransactionVersion } from '@stacks/transactions';
|
||||||
getAddressFromPrivateKey,
|
|
||||||
TransactionVersion
|
|
||||||
} from "@stacks/transactions";
|
|
||||||
|
|
||||||
export function getRandomInt(min, max) {
|
export function getRandomInt(min, max) {
|
||||||
min = Math.ceil(min);
|
min = Math.ceil(min);
|
||||||
@@ -16,9 +13,6 @@ export function sleep(ms) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAccountAddress(
|
export function getAccountAddress(account: Account, version: TransactionVersion = TransactionVersion.Mainnet): string {
|
||||||
account: Account,
|
|
||||||
version: TransactionVersion = TransactionVersion.Mainnet
|
|
||||||
): string {
|
|
||||||
return getAddressFromPrivateKey(account.stxPrivateKey, version);
|
return getAddressFromPrivateKey(account.stxPrivateKey, version);
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { PayloadType, ContractCallPayload, getAbi, addressToString, StacksTransaction } from '@stacks/transactions';
|
||||||
PayloadType,
|
|
||||||
ContractCallPayload,
|
|
||||||
getAbi,
|
|
||||||
addressToString,
|
|
||||||
StacksTransaction
|
|
||||||
} from "@stacks/transactions";
|
|
||||||
|
|
||||||
// rules for transaction sponsorship eligibility
|
// rules for transaction sponsorship eligibility
|
||||||
// customize this if you are forking this repo
|
// customize this if you are forking this repo
|
||||||
@@ -23,19 +17,17 @@ export async function validateTransaction(transaction: StacksTransaction) {
|
|||||||
const contractAddress = addressToString(payload.contractAddress);
|
const contractAddress = addressToString(payload.contractAddress);
|
||||||
const contractName = payload.contractName.content.toString();
|
const contractName = payload.contractName.content.toString();
|
||||||
const functionName = payload.functionName.content.toString();
|
const functionName = payload.functionName.content.toString();
|
||||||
const abi = await getAbi(contractAddress, contractName, "mainnet");
|
const abi = await getAbi(contractAddress, contractName, 'mainnet');
|
||||||
const func = abi.functions.find((func) => {
|
const func = abi.functions.find((func) => {
|
||||||
return func.name === functionName;
|
return func.name === functionName;
|
||||||
})
|
});
|
||||||
|
|
||||||
if (func.args.length !== 3) {
|
if (func.args.length !== 3) {
|
||||||
throw new Error('Transaction is not a NFT transfer contract call');
|
throw new Error('Transaction is not a NFT transfer contract call');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check against sip-09 interface for transfer
|
// check against sip-09 interface for transfer
|
||||||
if (func.args[0].type !== 'uint128'
|
if (func.args[0].type !== 'uint128' || func.args[1].type !== 'principal' || func.args[2].type !== 'principal') {
|
||||||
|| func.args[1].type !== 'principal'
|
|
||||||
|| func.args[2].type !== 'principal') {
|
|
||||||
throw new Error('Transaction is not a NFT transfer contract call');
|
throw new Error('Transaction is not a NFT transfer contract call');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user