resubmitGaslessRequest
Resend a gasless submit whose outcome was never established — byte for byte, under its original idempotency key.
import { getGaslessUnconfirmedSubmit, relayInstantOperations, resubmitGaslessRequest } from "@symmio/trading-core";
try {
await relayInstantOperations(config, parameters);
} catch (err) {
const pending = getGaslessUnconfirmedSubmit(err);
if (!pending) throw err;
await store.save(pending); // survive a reload before retrying
const receipt = await resubmitGaslessRequest(config, pending);
await store.clear(pending.idempotencyKey);
}A submit that fails ambiguously — no response, a timeout, a 502/504, or a 202 with no request_id — may still have been accepted. The SDK resends it once itself after a short backoff, and if that does not land either — however the resend is answered, a 429 included — it throws GASLESS_SUBMIT_UNCONFIRMED carrying the exact bytes it sent. This action replays them.
The vendor’s contract makes that safe: the same key with the same payload either lands the request or returns the record the service already created. Rebuilding the payload does not — the signatures inside it cover specific nonces and deadlines, and a regenerated body under a bound key is answered with 409 IDEMPOTENCY_KEY_CONFLICT.
Never respond to an unconfirmed submit by running the intent through the wallet. The relayed transaction may execute at any moment; a wallet-paid duplicate would execute it twice. Resubmitting is the only safe recovery, and it stays safe however many times you repeat it.
Parameters
submitGaslessUnconfirmedSubmitrequiredThe record from getGaslessUnconfirmedSubmit(err), passed through unmodified.
submit.chainIdnumberThe chain the submit was made on. The replay resolves this chain’s gasless config, never the config default.
submit.service"operations" | "deposits"The service that stores the workflow, derived from path.
submit.path"/gateway/relay-instant" | "/deposit-settlements/new-account" | "/deposit-settlements/existing-account"The gateway route the body was POSTed to. A closed union: an open string would let arbitrary paths be POSTed with a signed body.
submit.bodyunknownThe exact JSON that was sent. Sent again as-is.
submit.idempotencyKeystringThe key the submit carried, and that this replay reuses.
Returns
Promise<GaslessSubmitReceipt | GaslessDepositSubmitReceipt>requestIdstringThe workflow id — the one the original submit created, when it was accepted after all.
idempotencyKeystringThe replayed key, unchanged.
A /gateway/relay-instant replay returns the operations receipt (GaslessSubmitReceipt); either settlement path returns the deposit receipt (GaslessDepositSubmitReceipt). Discriminate on submit.service, or on the presence of depositAddress.
A deposit replay re-runs the original submit’s safety check: the wallet’s deterministic address is read before the POST and compared with the acceptance, so a replay can never sweep a wallet you did not fund.
Throws
SymmError("config", "GASLESS_NOT_CONFIGURED")/("config", "GASLESS_UNSUPPORTED_CONTRACTS_VERSION")— for the chain named in the record.SymmError("validation", "GASLESS_RESUBMIT_BODY_INVALID")— the record is not a submit this SDK produced (no JSON object body, or nouserAddress/ownerto attribute the acceptance to).SymmApiErrorwith codeGASLESS_SUBMIT_UNCONFIRMED— the replay was itself inconclusive. Keep the record and try again later; it never expires the way a signature does, and the service still holds the key.SymmApiErrorwith codeGASLESS_RELAY_SUBMIT_FAILED/GASLESS_SETTLEMENT_SUBMIT_FAILED— the service answered with a verdict. A409 IDEMPOTENCY_KEY_CONFLICT(isGaslessIdempotencyConflictError) means the record was modified before the replay.SymmApiErrorwith codeGASLESS_DEPOSIT_WALLET_MISMATCH— the acceptance named another wallet or deposit address.
Persist the record, not just the id. A GaslessUnconfirmedSubmit is the only artefact that can recover a workflow the
relayer may already be running, and it is worthless once rebuilt. Store it under (environment, protocolInstance, service, idempotencyKey) alongside your pending workflows — and keep it out of third-party logging: body holds the
user’s signed operations, and a signature the service never executed stays valid until its deadline.
The React binding is useResubmitGaslessRequest, which confirms the replayed request before it resolves and invalidates what it changed.