From 47589840ed22d53ed71a606284afbfd5d7d9b9e5 Mon Sep 17 00:00:00 2001
From: r-near <163825889+r-near@users.noreply.github.com>
Date: Wed, 8 Apr 2026 10:10:39 -0700
Subject: [PATCH 1/4] feat: replace postActions/extraMsg with safe_deposit for
UTXO deposits
The bridge API now uses view calls instead of on-chain transactions to
generate deposit addresses, avoiding NotEnoughBalance errors. Replace
postActions/extraMsg with safe_deposit on getUtxoDepositAddress, and
use safe_verify_deposit when finalizing deposits with safe_deposit.
Matches bridge-sdk-rs#260.
---
docs/reference/core.mdx | 60 +++++++++-----------------------
docs/reference/near.mdx | 7 +++-
packages/core/src/api.ts | 19 +++++-----
packages/core/src/bridge.ts | 27 ++++----------
packages/core/src/index.ts | 1 +
packages/core/tests/api.test.ts | 18 +++-------
packages/near/src/builder.ts | 6 ++--
packages/near/src/index.ts | 1 +
packages/near/src/types.ts | 10 ++++++
packages/near/tests/utxo.test.ts | 25 ++++++++++++-
10 files changed, 82 insertions(+), 92 deletions(-)
diff --git a/docs/reference/core.mdx b/docs/reference/core.mdx
index 870f8ea9..a8e5d17e 100644
--- a/docs/reference/core.mdx
+++ b/docs/reference/core.mdx
@@ -20,6 +20,7 @@ import {
type TransferStatus,
type Chain,
type PostAction,
+ type SafeDeposit,
type UtxoChainParam,
type UtxoDepositAddressResponse,
@@ -427,12 +428,14 @@ bridge.getUtxoDepositAddress(
Optional configuration for the deposit.
-
- Post-actions to execute after the deposit is finalized on NEAR. Used for automatic bridging to other chains.
-
-
-
- Extra message to include in the deposit.
+
+ Safe deposit message. When provided, the deposit will use `safe_verify_deposit` on finalization.
+
+
+
+ Message for the safe deposit.
+
+
@@ -454,10 +457,6 @@ bridge.getUtxoDepositAddress(
The recipient on NEAR.
-
-
- Post-actions if any were provided.
-
@@ -474,16 +473,12 @@ const deposit = await bridge.getUtxoDepositAddress(
console.log(`Send BTC to: ${deposit.address}`)
-// With post-actions for automatic bridging to Ethereum
-const depositWithBridge = await bridge.getUtxoDepositAddress(
+// With safe deposit
+const safeDeposit = await bridge.getUtxoDepositAddress(
ChainKind.Btc,
"alice.near",
{
- postActions: [{
- receiver_id: "omni.bridge.near",
- amount: "0",
- msg: JSON.stringify({ recipient: "eth:0x..." }),
- }]
+ safeDeposit: { msg: "safe deposit" }
}
)
```
@@ -860,8 +855,7 @@ Gets a deposit address for Bitcoin or Zcash.
api.getUtxoDepositAddress(
chain: UtxoChainParam,
recipient: string,
- postActions?: PostAction[] | null,
- extraMsg?: string | null
+ safeDeposit?: SafeDeposit | null
): Promise
```
@@ -875,36 +869,16 @@ api.getUtxoDepositAddress(
NEAR recipient account ID.
-
- Optional post-actions to execute after deposit finalization.
-
-
-
- NEAR account to receive the action.
-
-
-
- Amount to send with the action.
-
+
+ Optional safe deposit message.
+
- Message/arguments for the action.
-
-
-
- Optional gas limit.
-
-
-
- Optional memo.
+ Message for the safe deposit.
-
- Optional extra message.
-
-
### Returns
diff --git a/docs/reference/near.mdx b/docs/reference/near.mdx
index 009a1d0e..185900c3 100644
--- a/docs/reference/near.mdx
+++ b/docs/reference/near.mdx
@@ -61,6 +61,7 @@ import {
type UtxoDepositFinalizationParams,
type UtxoDepositMsg,
type UtxoPostAction,
+ type UtxoSafeDeposit,
type UtxoWithdrawalInitParams,
type UtxoWithdrawalOutput,
type UtxoWithdrawalVerifyParams,
@@ -785,7 +786,7 @@ Methods for interacting with the Bitcoin and Zcash UTXO connectors on NEAR.
### buildUtxoDepositFinalization
-Build a transaction to finalize a UTXO deposit on NEAR. Calls `verify_deposit` on the connector contract.
+Build a transaction to finalize a UTXO deposit on NEAR. Calls `verify_deposit` (or `safe_verify_deposit` when `safe_deposit` is provided) on the connector contract.
#### Signature
@@ -814,6 +815,10 @@ buildUtxoDepositFinalization(params: UtxoDepositFinalizationParams): NearUnsigne
Optional extra message.
+
+
+ Safe deposit message. When provided, uses `safe_verify_deposit` instead of `verify_deposit`.
+
diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts
index 0817848f..8b778ee4 100644
--- a/packages/core/src/api.ts
+++ b/packages/core/src/api.ts
@@ -195,6 +195,10 @@ const PostActionSchema = z.object({
})
export type PostAction = z.infer
+export interface SafeDeposit {
+ msg: string
+}
+
const UtxoDepositAddressResponseSchema = z.object({
address: z.string(),
})
@@ -358,19 +362,15 @@ export class BridgeAPI {
async getUtxoDepositAddress(
chain: UtxoChainParam,
recipient: string,
- postActions?: PostAction[] | null,
- extraMsg?: string | null,
+ safeDeposit?: SafeDeposit | null,
): Promise {
const body: Record = {
chain,
recipient,
}
- if (postActions !== undefined && postActions !== null) {
- body["post_actions"] = postActions
- }
- if (extraMsg !== undefined && extraMsg !== null) {
- body["extra_msg"] = extraMsg
+ if (safeDeposit !== undefined && safeDeposit !== null) {
+ body["safe_deposit"] = safeDeposit
}
const url = this.buildUrl("/api/v3/utxo/get_user_deposit_address")
@@ -401,9 +401,8 @@ export class BridgeAPI {
async getUtxoUserDepositAddress(
chain: UtxoChainParam,
recipient: string,
- postActions?: PostAction[] | null,
- extraMsg?: string | null,
+ safeDeposit?: SafeDeposit | null,
): Promise {
- return this.getUtxoDepositAddress(chain, recipient, postActions, extraMsg)
+ return this.getUtxoDepositAddress(chain, recipient, safeDeposit)
}
}
diff --git a/packages/core/src/bridge.ts b/packages/core/src/bridge.ts
index b3b05e17..6836a014 100644
--- a/packages/core/src/bridge.ts
+++ b/packages/core/src/bridge.ts
@@ -3,7 +3,7 @@
*/
import { Near } from "near-kit"
-import { BridgeAPI, type Chain, type PostAction, type UtxoChainParam } from "./api.js"
+import { BridgeAPI, type Chain, type SafeDeposit, type UtxoChainParam } from "./api.js"
import { type ChainAddresses, getAddresses } from "./config.js"
import { ValidationError } from "./errors.js"
import {
@@ -28,14 +28,10 @@ export interface BridgeConfig {
*/
export interface UtxoDepositOptions {
/**
- * Post-actions to execute after the deposit is finalized on NEAR.
- * Used for automatic bridging to other chains.
+ * Safe deposit message. When provided, the deposit will use `safe_verify_deposit`
+ * which requires a NEAR deposit but doesn't need post-actions.
*/
- postActions?: PostAction[]
- /**
- * Extra message to include in the deposit
- */
- extraMsg?: string
+ safeDeposit?: SafeDeposit
}
/**
@@ -54,10 +50,6 @@ export interface UtxoDepositResult {
* The recipient on NEAR
*/
recipient: string
- /**
- * Post-actions if any were provided
- */
- postActions?: PostAction[]
}
/**
@@ -331,21 +323,14 @@ class BridgeImpl implements Bridge {
const response = await this.api.getUtxoDepositAddress(
chainParam,
recipient,
- options?.postActions,
- options?.extraMsg,
+ options?.safeDeposit,
)
- const result: UtxoDepositResult = {
+ return {
address: response.address,
chain: chainParam,
recipient,
}
-
- if (options?.postActions) {
- result.postActions = options.postActions
- }
-
- return result
}
}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 24a83def..0058dd95 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -11,6 +11,7 @@ export {
type BridgeAPIConfig,
type Chain,
type PostAction,
+ type SafeDeposit,
type Transfer,
type TransferStatus,
type UtxoChainParam,
diff --git a/packages/core/tests/api.test.ts b/packages/core/tests/api.test.ts
index 2ad7e50d..dddb1389 100644
--- a/packages/core/tests/api.test.ts
+++ b/packages/core/tests/api.test.ts
@@ -262,20 +262,10 @@ describe("BridgeAPI", () => {
})
})
- it("should handle post actions parameter", async () => {
- const postActions = [
- {
- receiver_id: "receiver.near",
- amount: "1000000000000000000000000",
- msg: "test message",
- },
- ]
- const response = await api.getUtxoDepositAddress(
- "btc",
- "recipient.near",
- postActions,
- "extra message",
- )
+ it("should handle safe_deposit parameter", async () => {
+ const response = await api.getUtxoDepositAddress("btc", "recipient.near", {
+ msg: "safe deposit message",
+ })
expect(response).toEqual({
address: "tb1qssh0ejglq0v53pwrsxlhxpxw29gfu6c4ls9eyy",
})
diff --git a/packages/near/src/builder.ts b/packages/near/src/builder.ts
index 7b45439f..1ead3d6a 100644
--- a/packages/near/src/builder.ts
+++ b/packages/near/src/builder.ts
@@ -607,6 +607,7 @@ class NearBuilderImpl implements NearBuilder {
const connector = this.getUtxoConnectorAddress(params.chain)
// Build deposit_msg for the contract (convert bigint amounts to strings)
+ const isSafeDeposit = params.depositMsg.safe_deposit != null
const depositMsg = {
recipient_id: params.depositMsg.recipient_id,
post_actions: params.depositMsg.post_actions?.map((action) => ({
@@ -617,6 +618,7 @@ class NearBuilderImpl implements NearBuilder {
gas: action.gas?.toString(),
})),
extra_msg: params.depositMsg.extra_msg,
+ safe_deposit: params.depositMsg.safe_deposit,
}
const args = {
@@ -630,10 +632,10 @@ class NearBuilderImpl implements NearBuilder {
const action: NearAction = {
type: "FunctionCall",
- methodName: "verify_deposit",
+ methodName: isSafeDeposit ? "safe_verify_deposit" : "verify_deposit",
args: encodeArgs(args),
gas: GAS.UTXO_VERIFY_DEPOSIT,
- deposit: 0n,
+ deposit: isSafeDeposit ? DEPOSIT.SAFE_VERIFY_DEPOSIT : 0n,
}
return {
diff --git a/packages/near/src/index.ts b/packages/near/src/index.ts
index da34ad39..aa410613 100644
--- a/packages/near/src/index.ts
+++ b/packages/near/src/index.ts
@@ -47,6 +47,7 @@ export {
type UtxoDepositFinalizationParams,
type UtxoDepositMsg,
type UtxoPostAction,
+ type UtxoSafeDeposit,
type UtxoWithdrawalInitParams,
type UtxoWithdrawalOutput,
type UtxoWithdrawalSignParams,
diff --git a/packages/near/src/types.ts b/packages/near/src/types.ts
index b3bd294a..4bb32f8b 100644
--- a/packages/near/src/types.ts
+++ b/packages/near/src/types.ts
@@ -30,6 +30,7 @@ export const GAS = {
export const DEPOSIT = {
ONE_YOCTO: BigInt(parseAmount("1 yocto")),
MPC_SIGNING: BigInt(parseAmount("0.25 NEAR")),
+ SAFE_VERIFY_DEPOSIT: 1_200_000_000_000_000_000_000n,
} as const
/**
@@ -254,6 +255,14 @@ export interface UtxoPostAction {
gas?: bigint
}
+/**
+ * Safe deposit message for UTXO deposits.
+ * When present, the finalization uses `safe_verify_deposit` instead of `verify_deposit`.
+ */
+export interface UtxoSafeDeposit {
+ msg: string
+}
+
/**
* Deposit message for UTXO deposits
*/
@@ -261,6 +270,7 @@ export interface UtxoDepositMsg {
recipient_id: string
post_actions?: UtxoPostAction[]
extra_msg?: string
+ safe_deposit?: UtxoSafeDeposit
}
/**
diff --git a/packages/near/tests/utxo.test.ts b/packages/near/tests/utxo.test.ts
index 5e3edeed..b58b6e33 100644
--- a/packages/near/tests/utxo.test.ts
+++ b/packages/near/tests/utxo.test.ts
@@ -1,7 +1,7 @@
import { ChainKind } from "@omni-bridge/core"
import { describe, expect, it } from "vitest"
import { createNearBuilder } from "../src/builder.js"
-import { GAS, DEPOSIT } from "../src/types.js"
+import { DEPOSIT, GAS } from "../src/types.js"
describe("NearBuilder UTXO methods", () => {
const builder = createNearBuilder({ network: "testnet" })
@@ -98,6 +98,29 @@ describe("NearBuilder UTXO methods", () => {
expect(args.deposit_msg.post_actions).toHaveLength(1)
expect(args.deposit_msg.post_actions[0].amount).toBe("1000000")
})
+
+ it("uses safe_verify_deposit when safe_deposit is provided", () => {
+ const tx = builder.buildUtxoDepositFinalization({
+ chain: "btc",
+ depositMsg: {
+ recipient_id: "alice.testnet",
+ safe_deposit: { msg: "safe deposit" },
+ },
+ txBytes: [0x01, 0x02],
+ vout: 0,
+ txBlockBlockhash: "00000000000000000001abc123",
+ txIndex: 1,
+ merkleProof: ["hash1"],
+ signerId: "relayer.testnet",
+ })
+
+ expect(tx.actions[0]?.methodName).toBe("safe_verify_deposit")
+ expect(tx.actions[0]?.deposit).toBe(DEPOSIT.SAFE_VERIFY_DEPOSIT)
+
+ const argsJson = new TextDecoder().decode(tx.actions[0]?.args)
+ const args = JSON.parse(argsJson)
+ expect(args.deposit_msg.safe_deposit).toEqual({ msg: "safe deposit" })
+ })
})
describe("buildUtxoWithdrawalInit", () => {
From 110473910c04337bff6010fd81d09f68984f7139 Mon Sep 17 00:00:00 2001
From: r-near <163825889+r-near@users.noreply.github.com>
Date: Wed, 8 Apr 2026 10:10:45 -0700
Subject: [PATCH 2/4] fix: resolve noNonNullAssertion lint warnings in starknet
encoding
---
packages/starknet/src/encoding.ts | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/packages/starknet/src/encoding.ts b/packages/starknet/src/encoding.ts
index 1365f73f..c26718af 100644
--- a/packages/starknet/src/encoding.ts
+++ b/packages/starknet/src/encoding.ts
@@ -24,7 +24,11 @@ export function decodeByteArray(data: string[], offset: number): [string, number
throw new Error(`decodeByteArray: offset ${offset} out of bounds (length ${data.length})`)
}
- const numFullWords = Number(BigInt(data[offset]!))
+ const numFullWordsRaw = data[offset]
+ if (numFullWordsRaw === undefined) {
+ throw new Error(`decodeByteArray: missing data at offset ${offset}`)
+ }
+ const numFullWords = Number(BigInt(numFullWordsRaw))
const totalFelts = 1 + numFullWords + 2
if (offset + totalFelts > data.length) {
@@ -34,11 +38,13 @@ export function decodeByteArray(data: string[], offset: number): [string, number
}
const pendingWordIdx = offset + 1 + numFullWords
+ const pendingWord = data[pendingWordIdx] as string
+ const pendingWordLen = data[pendingWordIdx + 1] as string
const decoded = byteArray.stringFromByteArray({
data: data.slice(offset + 1, offset + 1 + numFullWords),
- pending_word: data[pendingWordIdx]!,
- pending_word_len: Number(BigInt(data[pendingWordIdx + 1]!)),
+ pending_word: pendingWord,
+ pending_word_len: Number(BigInt(pendingWordLen)),
})
return [decoded, offset + totalFelts]
From f6d49ef15eff430a18e83b79514a3e1f221045eb Mon Sep 17 00:00:00 2001
From: r-near <163825889+r-near@users.noreply.github.com>
Date: Wed, 8 Apr 2026 10:18:07 -0700
Subject: [PATCH 3/4] fix: address PR feedback on type safety and constant
style
---
packages/near/src/types.ts | 2 +-
packages/starknet/src/encoding.ts | 7 +++++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/packages/near/src/types.ts b/packages/near/src/types.ts
index 4bb32f8b..eebe9f77 100644
--- a/packages/near/src/types.ts
+++ b/packages/near/src/types.ts
@@ -30,7 +30,7 @@ export const GAS = {
export const DEPOSIT = {
ONE_YOCTO: BigInt(parseAmount("1 yocto")),
MPC_SIGNING: BigInt(parseAmount("0.25 NEAR")),
- SAFE_VERIFY_DEPOSIT: 1_200_000_000_000_000_000_000n,
+ SAFE_VERIFY_DEPOSIT: BigInt(parseAmount("0.0012 NEAR")),
} as const
/**
diff --git a/packages/starknet/src/encoding.ts b/packages/starknet/src/encoding.ts
index c26718af..7ba42482 100644
--- a/packages/starknet/src/encoding.ts
+++ b/packages/starknet/src/encoding.ts
@@ -38,8 +38,11 @@ export function decodeByteArray(data: string[], offset: number): [string, number
}
const pendingWordIdx = offset + 1 + numFullWords
- const pendingWord = data[pendingWordIdx] as string
- const pendingWordLen = data[pendingWordIdx + 1] as string
+ const pendingWord = data[pendingWordIdx]
+ const pendingWordLen = data[pendingWordIdx + 1]
+ if (pendingWord === undefined || pendingWordLen === undefined) {
+ throw new Error(`decodeByteArray: missing pending_word data at offset ${pendingWordIdx}`)
+ }
const decoded = byteArray.stringFromByteArray({
data: data.slice(offset + 1, offset + 1 + numFullWords),
From 44daec76ff1b9f9bdf22a60a0f912d6161e334c5 Mon Sep 17 00:00:00 2001
From: r-near <163825889+r-near@users.noreply.github.com>
Date: Wed, 8 Apr 2026 10:19:44 -0700
Subject: [PATCH 4/4] chore: add changeset for safe_deposit
---
.changeset/safe-deposit-utxo.md | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .changeset/safe-deposit-utxo.md
diff --git a/.changeset/safe-deposit-utxo.md b/.changeset/safe-deposit-utxo.md
new file mode 100644
index 00000000..9ae7d346
--- /dev/null
+++ b/.changeset/safe-deposit-utxo.md
@@ -0,0 +1,6 @@
+---
+"@omni-bridge/core": minor
+"@omni-bridge/near": minor
+---
+
+Replace `postActions`/`extraMsg` with `safe_deposit` on `getUtxoDepositAddress`. NEAR builder now calls `safe_verify_deposit` when `safe_deposit` is provided.