Sell Liquidity
Fulfil Magma orders as a seller - accept, reject, and confirm channel openings. Authenticated mutations with full code examples.
Once you've created an offer, buyers can place orders against it. This page covers the three seller mutations that move an order from creation to a confirmed channel: accept, reject, and add transaction.
All three live under market.order.seller.* and require an authenticated API key.
Always check the order's status before sending a mutation. Each operation is only valid in a specific state - sending one at the wrong time will fail. See Order Lifecycle.
Seller's view of the lifecycle
1. Accept an order
Required state: WAITING_FOR_SELLER_APPROVAL.
Provide a BOLT11 HODL invoice. The buyer will pay it, and the payment is held until you confirm channel opening. The invoice must have an expiry of at least 48 hours.
mutation AcceptOrder($input: SellerAcceptOrdersInput!) {
market {
order {
seller {
accept(input: $input) {
success
}
}
}
}
}{
"input": {
"order_id": "ec562479-a4b8-44f4-95b4-150b310832de",
"payment_request": "lnbc125u1p..."
}
}curl -X POST https://magma.amboss.tech/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AMBOSS_API_KEY" \
-d '{
"query": "mutation($input: SellerAcceptOrdersInput!) { market { order { seller { accept(input: $input) { success } } } } }",
"variables": {
"input": {
"order_id": "ec562479-a4b8-44f4-95b4-150b310832de",
"payment_request": "lnbc125u1p..."
}
}
}'import { GraphQLClient, gql } from "graphql-request";
const magma = new GraphQLClient("https://magma.amboss.tech/graphql", {
headers: { Authorization: `Bearer ${process.env.AMBOSS_API_KEY}` },
});
const ACCEPT = gql`
mutation AcceptOrder($input: SellerAcceptOrdersInput!) {
market { order { seller { accept(input: $input) { success } } } }
}
`;
await magma.request(ACCEPT, {
input: {
order_id: orderId,
payment_request: hodlInvoice, // BOLT11 with ≥ 48h expiry
},
});SellerAcceptOrdersInput
| Field | Required | Type | Description |
|---|---|---|---|
order_id | ✅ | String | The order to accept. |
payment_request | ✅ | String | BOLT11 HODL invoice for the order's fees.seller amount. Expiry ≥ 48 hours. |
Generate the invoice as a HODL invoice (preimage held by you, released only on channel confirmation). A regular invoice will settle as soon as the buyer pays, which removes the non-custodial guarantee that backs Magma orders.
2. Reject an order
Required state: WAITING_FOR_SELLER_APPROVAL.
Use this if you can't or won't fulfil the order (insufficient on-chain funds, buyer doesn't meet your standards, etc.). The order moves to SELLER_REJECTED and the offer's locked_amount is released.
mutation RejectOrder($input: SellerRejectOrdersInput!) {
market {
order {
seller {
reject(input: $input) {
success
}
}
}
}
}{ "input": { "order_id": "ec562479-a4b8-44f4-95b4-150b310832de" } }curl -X POST https://magma.amboss.tech/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AMBOSS_API_KEY" \
-d '{
"query": "mutation($input: SellerRejectOrdersInput!) { market { order { seller { reject(input: $input) { success } } } } }",
"variables": { "input": { "order_id": "ec562479-a4b8-44f4-95b4-150b310832de" } }
}'const REJECT = gql`
mutation RejectOrder($input: SellerRejectOrdersInput!) {
market { order { seller { reject(input: $input) { success } } } }
}
`;
await magma.request(REJECT, { input: { order_id: orderId } });3. Confirm channel opening (add transaction)
Required state: WAITING_FOR_CHANNEL_OPEN.
After the buyer pays your HODL invoice, you have a deadline to broadcast the channel funding TX. Once it's in the mempool, call add_transaction to tell Magma which TX is the channel.
The tx_id must include the output index as TRANSACTION_ID:OUTPUT_INDEX. The output identifies which UTXO in the TX is the channel anchor.
mutation AddTransaction($input: SellerAddTransactionInput!) {
market {
order {
seller {
add_transaction(input: $input) {
success
}
}
}
}
}{
"input": {
"order_id": "ec562479-a4b8-44f4-95b4-150b310832de",
"tx_id": "5e8a3f...c4f1:0"
}
}curl -X POST https://magma.amboss.tech/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AMBOSS_API_KEY" \
-d '{
"query": "mutation($input: SellerAddTransactionInput!) { market { order { seller { add_transaction(input: $input) { success } } } } }",
"variables": {
"input": {
"order_id": "ec562479-a4b8-44f4-95b4-150b310832de",
"tx_id": "5e8a3f...c4f1:0"
}
}
}'const ADD_TX = gql`
mutation AddTransaction($input: SellerAddTransactionInput!) {
market { order { seller { add_transaction(input: $input) { success } } } }
}
`;
await magma.request(ADD_TX, {
input: {
order_id: orderId,
tx_id: `${fundingTxId}:${outputIndex}`,
},
});SellerAddTransactionInput
| Field | Required | Type | Description |
|---|---|---|---|
order_id | ✅ | String | The order being fulfilled. |
tx_id | ✅ | String | TRANSACTION_ID:OUTPUT_INDEX of the funding TX. |
Once the funding TX confirms with the agreed capacity, the order moves to VALID_CHANNEL_OPENING and the HODL invoice settles - you get paid. If the channel doesn't match the agreed terms, the order ends in INVALID_CHANNEL_OPENING and the buyer's payment is refunded automatically.
4. List orders you need to act on
The OrderInput.action_needed filter returns just the orders where the seller is expected to do something.
query MySales {
user {
market {
orders {
sales(input: { action_needed: true }, page: { limit: 25, offset: 0 }) {
list {
id
status
timeout
amount { satoshi { sats usd } }
destination { pubkey alias }
}
total
}
}
}
}
}Poll this query at 30–60s intervals or pair it with a UI that auto-refreshes. action_needed: true maps to exactly three statuses:
WAITING_FOR_SELLER_APPROVAL→ accept or reject before the order'stimeoutWAITING_FOR_CHANNEL_OPEN→ broadcast TX and calladd_transactionSELLER_SENT_TRANSACTION→ we've seen your TX but not yet a confirmation. Nothing to do unless it dropped out of the mempool, in which case rebroadcast and calladd_transactionagain with the new TX.
If you want a different set, pass input: { status: [...] } instead. Both filters are applied server-side, so filtering here is cheaper than fetching everything and filtering in your own code. Results are newest first, and total is the full match count, not the page size.
To list every sale rather than just the actionable ones, drop input entirely and page through with page: { limit, offset }. This is the replacement for the legacy getUser.market.offer_orders.list, which returned an unpaginated list. See Migrate from the Legacy API.
Reference flow (seller side)
import { GraphQLClient, gql } from "graphql-request";
const magma = new GraphQLClient("https://magma.amboss.tech/graphql", {
headers: { Authorization: `Bearer ${process.env.AMBOSS_API_KEY}` },
});
const MY_SALES = gql`
query MySales {
user { market { orders { sales(input: { action_needed: true }) {
list {
id status destination { pubkey } amount { satoshi { sats } }
}
} } } }
}
`;
const ACCEPT = gql`
mutation Accept($input: SellerAcceptOrdersInput!) {
market { order { seller { accept(input: $input) { success } } } }
}
`;
const ADD_TX = gql`
mutation AddTx($input: SellerAddTransactionInput!) {
market { order { seller { add_transaction(input: $input) { success } } } }
}
`;
while (true) {
const { user } = await magma.request(MY_SALES);
for (const order of user.market.orders.sales.list) {
if (order.status === "WAITING_FOR_SELLER_APPROVAL") {
const invoice = await myNode.createHodlInvoice({
amount: order.amount.satoshi.sats,
expirySec: 48 * 3600,
});
await magma.request(ACCEPT, {
input: { order_id: order.id, payment_request: invoice },
});
}
if (order.status === "WAITING_FOR_CHANNEL_OPEN") {
const { txid, outputIndex } = await myNode.openChannel({
peer: order.destination.pubkey,
sats: order.amount.satoshi.sats,
});
await magma.request(ADD_TX, {
input: { order_id: order.id, tx_id: `${txid}:${outputIndex}` },
});
}
}
await new Promise((r) => setTimeout(r, 30_000));
}Next steps
- Create Offers - post the listings buyers fill
- Order Lifecycle - every status, what triggers each transition
- Tracking Orders - polling pattern (same for sellers)
- Errors - failure modes and refunds