Tracking a Transfer
Prerequisites
Before tracking a transfer, you need to send it. See Sending a Transfer to get the Transfer ID.
Transfer ID
Technical Details
Transfer ID is the transaction hash of the event on the Proxy contract.
┌─────────────────────────────────────────────────────────────────┐
│ Proxy Contract │
│ │
│ 1. Receives tokens via transferNotification() │
│ 2. Calls _emitTvmEvent() or _deployEvmEvent() │
│ 3. Emits TvmTvmNative or TvmTvmAlien event │
│ 4. Transaction hash of this event = TRANSFER ID │
└─────────────────────────────────────────────────────────────────┘Location in Blockchain
Transaction (on Proxy contract) ← Transaction Hash = TRANSFER ID
├── In Message (incoming tokens)
├── Out Messages
│ ├── Internal Messages (internal transfers)
│ └── External Out Message ← EVENT (TvmTvmNative/TvmTvmAlien)
└── Account (updated state)Event Types
| Event | TokenType | Description |
|---|---|---|
TvmTvmNative | Native | Native token transfer (lock → mint) |
TvmTvmAlien | Alien | Alien token transfer (burn → unlock) |
Transfer Statuses
Two Status Levels
The system has two status levels:
| Level | Where Stored | Purpose |
|---|---|---|
| TransferStatus | Bridge Aggregator API | Aggregated status for UI/integrations |
| Event Contract Status | On-chain (Event contract) | Detailed event verification state |
Relationship: Bridge Aggregator API aggregates on-chain Event contract statuses into a simplified TransferStatus:
Event Contract Status → TransferStatus (API)
─────────────────────────────────────────────────────────────────────
Initializing, Pending, Verified → Pending
Confirmed, LiquidityProvided → Completed
Rejected, Cancelled → FailedTransferStatus (API level)
Used in Bridge Aggregator API responses (/v2/transfers/status, /v2/transfers/search).
| Status | Code | Description |
|---|---|---|
Pending | 1 | Event created, awaiting confirmation in destination network |
Completed | 2 | Transfer fully completed (tokens delivered) |
Failed | 3 | Transfer rejected by relays |
Event Contract Status (on-chain level)
Detailed Event contract statuses in the blockchain. Used to understand the verification stage of the event.
| Status | Code | Description | → TransferStatus |
|---|---|---|---|
Initializing | 0 | Event contract deployed | Pending |
Pending | 1 | Awaiting verification | Pending |
Confirmed | 2 | Confirmed, proxy called, tokens delivered | Completed |
Rejected | 3 | Rejected | Failed |
Cancelled | 4 | Cancelled by user | Failed |
LimitReached | 5 | Daily limit exceeded, requires liquidity | Pending |
LiquidityRequested | 6 | Liquidity request created | Pending |
LiquidityProvided | 7 | Liquidity provided, tokens delivered | Completed |
Verified | 8 | Merkle proof verified, awaiting token deploy | Pending |
Trustless transition: Pending → Verified → Confirmed
Status Transition Diagram

Getting Status by Transfer ID
API Endpoint
POST https://tetra-history-api.chainconnect.com/v2/transfers/status
Content-Type: application/jsonTest Environment
For testing, use https://history-api-test.chainconnect.com/v2/transfers/status
Request Parameters
Important
According to the OpenAPI specification, the field is called outgoingTransactionHash (not outgoingMessageHash)
| Parameter | Type | Required | Description |
|---|---|---|---|
outgoingTransactionHash | string | ✅ | Transfer ID (hex, 64 characters) |
dappChainId | number | ✅ | Chain ID of source network (TON = -239, Tycho = 2000) |
timestampCreatedFrom | number | ❌ | Unix timestamp for filtering (optional) |
Request Example (TvmTvm)
curl -X POST 'https://tetra-history-api.chainconnect.com/v2/transfers/status' \
-H 'Content-Type: application/json' \
-d '{
"tvmTvm": {
"outgoingTransactionHash": "abc123def456...",
"dappChainId": -239,
"timestampCreatedFrom": null
}
}'Response Example
{
"transfer": {
"tvmTvm": {
"transferStatus": "Pending",
"timestampCreatedAt": 1767972717,
"outgoing": {
"tokenType": "Native",
"chainId": -239,
"userAddress": "0:3333...cccc",
"tokenAddress": "0:1111...aaaa",
"proxyAddress": "0:6666...ffff",
"volumeExec": "0.1000",
"volumeUsdtExec": "0",
"feeVolumeExec": "0",
"messageHash": "abc123def456...",
"transactionHash": "def789abc012..."
},
"incoming": {
"tokenType": null,
"chainId": 2000,
"userAddress": "0:2222...bbbb",
"tokenAddress": null,
"proxyAddress": null,
"volumeExec": null,
"volumeUsdtExec": null,
"feeVolumeExec": null,
"messageHash": null,
"transactionHash": null
}
}
},
"notInstantTransfer": null,
"proofPayload": {
"txBlockProof": "te6ccgECCg...",
"txProof": "te6ccgEBBw...",
"messageHash": "abc123def456...",
"outMessageIndex": 0,
"event": {
"tokenType": "Native",
"chainId": 2000,
"token": "0:1111...aaaa",
"amount": "99000",
"recipient": "0:2222...bbbb",
"value": "150000000",
"expectedGas": "0",
"remainingGasTo": "0:3333...cccc",
"sender": "0:3333...cccc",
"payload": "te6ccgEBAQEAAgAAAA==",
"nativeProxyWallet": "0:9999...2222",
"name": "Tether USD",
"symbol": "USD₮",
"decimals": 6
},
"abiTx": {
"tx": "te6ccgEBBwEA6AABiw/X8c8...",
"executionAddress": "0:8888...1111",
"attachedValue": "2000000000",
"abiMethod": "deployEvent",
"abi": "...",
"params": "{\"_eventVoteData\":{...}}"
}
}
}Response Field Descriptions
transfer.tvmTvm — transfer data
| Field | Type | Description |
|---|---|---|
transferStatus | string | Status: "Pending", "Completed", "Failed" |
timestampCreatedAt | number | Unix timestamp of transfer creation |
outgoing / incoming — transfer side data
| Field | Type | Description |
|---|---|---|
tokenType | string | null | "Native" or "Alien" |
chainId | number | Network Chain ID |
userAddress | string | User address (sender/recipient) |
tokenAddress | string | null | Token address in this network |
proxyAddress | string | null | Proxy contract address |
volumeExec | string | null | Amount in human-readable format |
volumeUsdtExec | string | null | Amount in USDT equivalent |
feeVolumeExec | string | null | Fee charged |
messageHash | string | null | Event message hash |
transactionHash | string | null | Transaction hash (Transfer ID for outgoing) |
proofPayload — data for non-credit transfers
| Field | Type | Description |
|---|---|---|
txBlockProof | string | Block proof (BOC base64) |
txProof | string | Transaction Merkle proof (BOC base64) |
messageHash | string | Event message hash |
outMessageIndex | number | Index of outgoing message in transaction |
event | object | Event data (see below) |
abiTx | object | null | Ready transaction for deployEvent (see below) |
proofPayload.abiTx — ready transaction for non-credit
| Field | Type | Description |
|---|---|---|
tx | string | Transaction payload (BOC base64) |
executionAddress | string | EventConfiguration contract address |
attachedValue | string | Required gas in nano-units |
abiMethod | string | Contract method (deployEvent) |
abi | string | Contract ABI |
params | string | Call parameters (JSON string) |
proofPayload.event — event data
| Field | Type | Description |
|---|---|---|
tokenType | string | "Native" or "Alien" |
chainId | number | Destination network Chain ID |
token | string | Token address |
amount | string | Amount in nano-units |
recipient | string | Recipient address |
value | string | Attached value (gas) |
expectedGas | string | Expected gas |
remainingGasTo | string | Address for gas return |
sender | string | Sender address |
payload | string | Additional payload (BOC base64) |
name | string | Token name |
symbol | string | Token symbol |
decimals | number | Number of decimals |
notInstantTransfer — liquidity request data
Populated when the transfer requires liquidity (LimitReached → LiquidityRequested status).
| Field | Type | Description |
|---|---|---|
liquidityRequestAddress | string | Liquidity request contract address |
status | string | Request status |
amount | string | Requested amount |
Response Interpretation
| Field | Value | Action |
|---|---|---|
transferStatus: "Pending" | Waiting | Continue polling |
transferStatus: "Completed" | Completed | Success! |
transferStatus: "Failed" | Error | Handle error |
proofPayload != null | Proof ready | Can deploy event (non-credit) |
incoming.transactionHash != null | Second part executed | Transfer almost completed |
Transfer History
API Endpoint
POST https://tetra-history-api.chainconnect.com/v2/transfers/search
Content-Type: application/jsonRequest Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userAddresses | string[] | ❌ | Filter by user addresses (max 7) |
transferKinds | string[] | ❌ | Transfer type: ["TvmToTvm"] |
statuses | string[] | ❌ | Filter by statuses: ["Pending", "Completed", "Failed"] |
fromTvmChainId | number | ❌ | Source network Chain ID |
toTvmChainId | number | ❌ | Destination network Chain ID |
createdAtGe | number | ❌ | Unix timestamp >= (from date) |
createdAtLe | number | ❌ | Unix timestamp <= (to date) |
ordering | string | ❌ | Sorting: "CreatedAtAscending" or "CreatedAtDescending" |
limit | number | ✅ | Record limit (max count in response) |
offset | number | ✅ | Offset (for pagination) |
isNeedTotalCount | boolean | ✅ | Whether to return total transfer count |
Request Example
curl -X POST 'https://tetra-history-api.chainconnect.com/v2/transfers/search' \
-H 'Content-Type: application/json' \
-d '{
"userAddresses": ["0:3333...cccc"],
"transferKinds": ["TvmToTvm"],
"statuses": ["Pending", "Completed"],
"fromTvmChainId": -239,
"toTvmChainId": 2000,
"ordering": "CreatedAtDescending",
"limit": 10,
"offset": 0,
"isNeedTotalCount": true
}'Response Example
{
"transfers": [
{
"tvmTvm": {
"transferStatus": "Completed",
"timestampCreatedAt": 1767972717,
"outgoing": {
"tokenType": "Native",
"chainId": -239,
"userAddress": "0:3333...cccc",
"tokenAddress": "0:1111...aaaa",
"proxyAddress": "0:6666...ffff",
"volumeExec": "1.0000",
"transactionHash": "abc123..."
},
"incoming": {
"tokenType": "Alien",
"chainId": 2000,
"userAddress": "0:2222...bbbb",
"tokenAddress": "0:4444...dddd",
"proxyAddress": "0:7777...0000",
"volumeExec": "0.9990",
"transactionHash": "def456..."
}
}
}
],
"totalCount": 42
}Transfer ID for each transfer: transfers[i].tvmTvm.outgoing.transactionHash
Tracking Algorithm
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. GET TRANSFER ID │
│ After sending tokens to the Proxy contract, save the transaction │
│ hash of that transaction — this is the Transfer ID │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. REQUEST STATUS │
│ POST /v2/transfers/status with Transfer ID and Chain ID │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. CHECK transferStatus │
│ │
│ "Pending" → Transfer in progress, retry request in 5-10 sec │
│ "Completed" → Success! Tokens delivered to recipient │
│ "Failed" → Error, transfer rejected │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. FOR COMPLETED TRANSFER │
│ incoming.transactionHash — hash of token delivery transaction │
│ incoming.volumeExec — received amount (after fees) │
└─────────────────────────────────────────────────────────────────────────┘What to Check in Response
| Field | Meaning |
|---|---|
transferStatus: "Pending" | Transfer in progress — retry request |
transferStatus: "Completed" | Transfer completed — tokens delivered |
transferStatus: "Failed" | Transfer rejected — handle error |
incoming.transactionHash != null | Delivery transaction executed |
proofPayload != null | Merkle proof ready (for non-credit transfers) |
Credit vs Non-credit Transfers
| Type | Description | User Actions |
|---|---|---|
| Credit | Relays automatically deploy event and deliver tokens | Only track status |
| Non-credit | User deploys event contract themselves | Wait for proofPayload, deploy event |
For non-credit transfers:
- Wait for
proofPayloadto appear in response - Use the ready transaction from
proofPayload.abiTxto deploy the Event contract in the destination network (see Sending a Transfer) - After deploying Event contract, the transfer will complete automatically
Example: TypeScript polling
async function waitForTransferCompletion(
transferId: string,
chainId: number,
maxAttempts = 60,
initialDelayMs = 5000
): Promise<TransferStatusResponse> {
let delay = initialDelayMs;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await fetch('https://tetra-history-api.chainconnect.com/v2/transfers/status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tvmTvm: {
outgoingTransactionHash: transferId,
dappChainId: chainId,
},
}),
});
const status = await response.json();
if (status.transfer.tvmTvm?.transferStatus === 'Completed') {
console.log(`Transfer completed after ${attempt} attempts`);
return status;
}
if (status.transfer.tvmTvm?.transferStatus === 'Failed') {
throw new Error('Transfer failed');
}
console.log(`Attempt ${attempt}: status = ${status.transfer.tvmTvm?.transferStatus}`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 1.2, 30000); // Max 30 seconds
}
throw new Error('Transfer timeout');
}
// Usage
const transferId = 'abc123def456...'; // Transfer ID from Step 3 of sending a transfer
const result = await waitForTransferCompletion(transferId, -239);
console.log('Transfer completed:', result.transfer.tvmTvm?.incoming);Getting Status from Contract (on-chain)
In addition to Bridge Aggregator API, transfer status can be obtained directly from the Event contract in the blockchain. This is useful for:
- Verifying API data
- Working without dependency on centralized API
- Getting detailed on-chain status
Event Contract Address
Note
The contractAddress field is not currently available in the TvmTvmTransferMeta API response. Event contract address retrieval via API is planned for future releases.
Reading Status from Contract
import { TonClient, Address } from '@ton/ton';
// ABI for getDetails method of Event contract
const EVENT_CONTRACT_ABI = {
getDetails: {
name: 'getDetails',
inputs: [],
outputs: [
{ name: 'status', type: 'uint8' },
{ name: 'sender', type: 'address' },
{ name: 'recipient', type: 'address' },
{ name: 'amount', type: 'uint128' },
// ... other fields
],
},
};
// Mapping numeric statuses to strings
const EVENT_STATUS = {
0: 'Initializing',
1: 'Pending',
2: 'Confirmed',
3: 'Rejected',
4: 'Cancelled',
5: 'LimitReached',
6: 'LiquidityRequested',
7: 'LiquidityProvided',
8: 'Verified',
} as const;
async function getEventContractStatus(
client: TonClient,
eventContractAddress: string
): Promise<{ status: string; statusCode: number }> {
const address = Address.parse(`0:${eventContractAddress}`);
// Call contract get-method
const result = await client.runMethod(address, 'getDetails');
// Parse result
const statusCode = result.stack.readNumber();
const status = EVENT_STATUS[statusCode as keyof typeof EVENT_STATUS] || 'Unknown';
return { status, statusCode };
}
// Usage (once contractAddress becomes available in API)
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
});
// Event contract address (will be available in API response in future)
const eventContractAddress = 'aaaa...1111';
const { status, statusCode } = await getEventContractStatus(client, eventContractAddress);
console.log(`Event status: ${status} (code: ${statusCode})`);Full Event Contract Data
To get all event data, use the getDetails method:
interface EventDetails {
status: number;
sender: string;
recipient: string;
amount: bigint;
token: string;
chainId: number;
// ... other fields depend on event type
}
async function getFullEventDetails(
client: TonClient,
eventContractAddress: string
): Promise<EventDetails> {
const address = Address.parse(`0:${eventContractAddress}`);
const result = await client.runMethod(address, 'getDetails');
return {
status: result.stack.readNumber(),
sender: result.stack.readAddress().toString(),
recipient: result.stack.readAddress().toString(),
amount: result.stack.readBigNumber(),
token: result.stack.readAddress().toString(),
chainId: result.stack.readNumber(),
};
}Important
The getDetails response structure may differ for different Event contract versions (Native vs Alien). Check the contract ABI before parsing.