@extra-wallet/ugtp-sdk
v1.0.15
Published
TypeScript SDK for gasless transactions using smart accounts
Readme
GaslessTransact SDK
A TypeScript SDK for ERC-4337 gasless transactions. The SDK talks to a UGTP API that builds and submits UserOperations; it does not sign anything until you explicitly ask it to.
Installation
npm install @gaslesstransact/sdk ethers@6Lifecycle
All transactions follow the same three-step flow:
- Generate — POST your
callsto the API; get back an unsigned UserOp, its hash, and an estimated max fee (in fee-token units if a paymaster is used). - Sign — sign the returned
userOpHashwith your signer. - Send — submit the signed UserOp to the API. If a relayer lane has capacity, the API broadcasts
handleOpsand returns a relayer transaction hash; otherwise it returnsqueuedand you can poll byuserOpHash.
You can optionally poll or wait for a terminal status.
Quick Start
import { GaslessClient, BrowserSigner, NetworkId } from "@gaslesstransact/sdk";
const client = new GaslessClient({
apiUrl: "https://api.gaslesstransact.io/api/v1",
apiHeaders: { "x-api-key": "your-api-key" },
networkId: NetworkId.SEPOLIA,
signer: new BrowserSigner(),
});
// 1. Generate
const generated = await client.generateUserOperation({
calls: [{ to: "0xRecipient…", data: "0x", value: "0x0" }],
feeToken: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", // optional WETH on Sepolia
// transferred asset(s) the percentage transfer fee is charged on; one element per moved token
transferredAssets: [{ address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", amount: "0x0" }],
headers: { "x-request-id": "generate-1" }, // optional per-call headers
});
// 2. Sign
const signature = await client.signUserOperationHash(generated.userOpHash);
// 3. Send
const result = await client.sendUserOperation({
userOperation: { ...generated.userOperation, signature },
userOpHash: generated.userOpHash,
networkId: NetworkId.SEPOLIA,
});
// (optional) watch queued/submitted -> terminal status
const statusLookupHash = result.transactionHash ?? result.userOpHash;
const status = await client.waitForTransaction(statusLookupHash, {
timeoutMs: 120_000,
onStatus: (nextStatus) => {
console.log("status update", nextStatus.status, nextStatus.transactionHash);
},
});
console.log(status.status, status.transactionHash);Signers
The SDK ships two ISigner implementations:
BrowserSigner— wraps a browser EIP-1193 provider (e.g. MetaMask).PrivateKeySigner— signs with a raw private key (server-side or scripts).
Custom signers only need to implement:
interface ISigner {
getAddress(): Promise<string>;
signMessage(message: string): Promise<string>;
}Client Surface
A GaslessClient exposes:
client.account— smart account address derivation, address info, and deployment status via direct RPC reads.client.fee— approximate native/fee-token preview using gas prices, call-gas estimation, and the paymaster fee formula.client.transaction— low-level UGTP API transport for generate/send/status polling.- client methods for signing, waiting, and shared network metadata lookups.
API Reference
GaslessClient
new GaslessClient({ apiUrl, networkId, signer });networkIdshould use the sharedSupportedNetworkIdunion, typically viaNetworkId.ETHEREUM,NetworkId.ARBITRUM, etc.the SDK now uses the bundled shared public factory address metadata for local smart-account derivation instead of accepting caller-provided factory overrides
the bundled public contract metadata is sourced from the shared deployment registry in
shared/src/deployments.tspass
provider/providerswhen you want explicit read providers, orrpcUrl/rpcUrlsif you prefer raw RPC stringspass
apiHeaderswhen every API request needs headers such asx-api-key; generate/send/status methods also accept per-callheadersif no provider config is passed, the SDK falls back to built-in public read RPC defaults per supported network
generateUserOperation(params)→GenerateUserOpResultsignUserOperationHash(userOpHash)→ signaturesendUserOperation(params)→TransactionResult;transactionHashis present once submitted, while queued results can be polled byuserOpHashwaitForTransaction(transactionHashOrUserOpHash, options?)→ final observedTransactionStatusgetSupportedNetworks()→NetworkConfig[]getNetworkConfig(networkId?)→NetworkConfiggetSupportedTokens(networkId?)→TokenConfig[]
client.transaction
generateUserOperation(request)→GenerateUserOpResult- request must include
networkId,ownerAddress,senderAddress, andcalls - request includes a
transferredAssetsarray (each{ address, amount }, amount a hex quantity in that token's base units); the API quotes each asset's value into the selected fee token through trusted Uniswap V3 routes and sums them before signing the paymaster payload (a single op can move several different tokens) - request may include
headers; they are applied to that HTTP request and are not serialized into the JSON body - this is the low-level API transport shape;
GaslessClient.generateUserOperation(...)derivesownerAddressandsenderAddressfor you sendUserOperation(params)→TransactionResult; params may include per-callheadersgetStatus(transactionHashOrUserOpHash, networkId, options?)→TransactionStatus; options may includeheaderswaitForCompletion(transactionHashOrUserOpHash, networkId, options?)→ final observedTransactionStatus; options may includeheadersfor every poll- returns terminal status when confirmed/failed/dropped
- uses the API-provided polling hint internally between polls
- returns the last observed non-terminal status on timeout
- returns
unknownon timeout if no status could be observed yet
client.fee
estimate({ networkId, feeToken?, senderAddress?, calls?, transferredAssets })→FeeEstimate- when
callsandsenderAddressare supplied for a deployed account, the SDK encodes the SmartAccount calldata, estimates execution gas with a small buffer, and uses that in the preview - when
callsare omitted, or the smart account is not deployed yet, the SDK falls back to the transfer-style heuristic untilgenerateUserOperation(...)returns the precise signed cap GaslessClient.estimateFee(...)defaultsnetworkIdfrom the client and derivessenderAddressfrom the signer whencallsare supplied
client.account
getSmartWalletAddress(networkId, ownerAddress?)→ smart wallet addressgetSmartWalletAddressInfo(networkId, ownerAddress?)→ address infoisSmartWalletDeployed(networkId, ownerAddress?)→ boolean
If ownerAddress is omitted, the module falls back to the configured signer address.
The account module no longer calls the API. It derives the counterfactual address locally with SmartAccountFactory.getAddress(...) using the SDK's bundled public factory config, and checks deployment status with provider.getCode(...).
Provider Utilities
The SDK also exports:
getDefaultRpcUrl(networkId)→ built-in public read RPC URLcreateDefaultProvider(networkId)→ defaultJsonRpcProviderresolveProviderForNetwork(networkId, config)→ resolved read provider fromprovider/providers/rpcUrl/rpcUrlswith public fallbacknew NetworkProviderResolver(config)→ cached, validated per-network provider resolver used byAccount
HTTP Utilities
The SDK also exports:
new HttpClient({ baseUrl })→ reusable JSON-focused HTTP client with query-param support and automatic UGTPAPIResponseunwrappingHttpError→ normalized error type for non-2xx responses and API-level failures
Network Helpers
The SDK exports pure helpers for shared network metadata:
getSupportedNetworks()→NetworkConfig[]getNetworkConfig(networkId)→NetworkConfiggetSupportedTokens(networkId)→TokenConfig[]
Types
import type {
Call,
SendCallsParams,
GenerateUserOperationRequest,
GaslessClientConfig,
ProviderConfig,
UserOperation,
GenerateUserOpResult,
SendUserOperationParams,
TransactionResult,
TransactionStatus,
WaitForTransactionOptions,
NetworkConfig,
TokenConfig,
SmartWalletAddressInfo,
} from "@gaslesstransact/sdk";License
MIT
