Create a withdrawal
curl --request POST \
--url https://api.numofx.com/v1/withdrawals \
--header 'Content-Type: application/json' \
--data '
{
"action": {
"subaccount_id": "<string>",
"nonce": "<string>",
"module": "<string>",
"data": "<string>",
"expiry": "<string>",
"owner": "<string>",
"signer": "<string>"
},
"signature": "<string>"
}
'import requests
url = "https://api.numofx.com/v1/withdrawals"
payload = {
"action": {
"subaccount_id": "<string>",
"nonce": "<string>",
"module": "<string>",
"data": "<string>",
"expiry": "<string>",
"owner": "<string>",
"signer": "<string>"
},
"signature": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
action: {
subaccount_id: '<string>',
nonce: '<string>',
module: '<string>',
data: '<string>',
expiry: '<string>',
owner: '<string>',
signer: '<string>'
},
signature: '<string>'
})
};
fetch('https://api.numofx.com/v1/withdrawals', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.numofx.com/v1/withdrawals",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'action' => [
'subaccount_id' => '<string>',
'nonce' => '<string>',
'module' => '<string>',
'data' => '<string>',
'expiry' => '<string>',
'owner' => '<string>',
'signer' => '<string>'
],
'signature' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.numofx.com/v1/withdrawals"
payload := strings.NewReader("{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.numofx.com/v1/withdrawals")
.header("Content-Type", "application/json")
.body("{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.numofx.com/v1/withdrawals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"accepted": true,
"tx_hash": "<string>",
"receipt_status": "success",
"block_number": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>",
"revert": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Markets service
Create a withdrawal
Withdraws from a subaccount held by Matching with one signed WithdrawalModule action. The venue verifies the signature and that Matching records action.owner as the owner, simulates the withdrawal, and submits it through Matching.verifyAndMatch. Tokens are paid to action.owner. Every check fails closed.
POST
/
v1
/
withdrawals
Create a withdrawal
curl --request POST \
--url https://api.numofx.com/v1/withdrawals \
--header 'Content-Type: application/json' \
--data '
{
"action": {
"subaccount_id": "<string>",
"nonce": "<string>",
"module": "<string>",
"data": "<string>",
"expiry": "<string>",
"owner": "<string>",
"signer": "<string>"
},
"signature": "<string>"
}
'import requests
url = "https://api.numofx.com/v1/withdrawals"
payload = {
"action": {
"subaccount_id": "<string>",
"nonce": "<string>",
"module": "<string>",
"data": "<string>",
"expiry": "<string>",
"owner": "<string>",
"signer": "<string>"
},
"signature": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
action: {
subaccount_id: '<string>',
nonce: '<string>',
module: '<string>',
data: '<string>',
expiry: '<string>',
owner: '<string>',
signer: '<string>'
},
signature: '<string>'
})
};
fetch('https://api.numofx.com/v1/withdrawals', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.numofx.com/v1/withdrawals",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'action' => [
'subaccount_id' => '<string>',
'nonce' => '<string>',
'module' => '<string>',
'data' => '<string>',
'expiry' => '<string>',
'owner' => '<string>',
'signer' => '<string>'
],
'signature' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.numofx.com/v1/withdrawals"
payload := strings.NewReader("{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.numofx.com/v1/withdrawals")
.header("Content-Type", "application/json")
.body("{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.numofx.com/v1/withdrawals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": {\n \"subaccount_id\": \"<string>\",\n \"nonce\": \"<string>\",\n \"module\": \"<string>\",\n \"data\": \"<string>\",\n \"expiry\": \"<string>\",\n \"owner\": \"<string>\",\n \"signer\": \"<string>\"\n },\n \"signature\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"accepted": true,
"tx_hash": "<string>",
"receipt_status": "success",
"block_number": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>",
"revert": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Withdraws from a subaccount held by Matching — the kind every account created through the Numo app is. You sign
one
Action for the WithdrawalModule; the venue verifies it and submits it through Matching.verifyAndMatch,
which lends the account to the module for the call. The module pays the action’s owner, and the account stays in
Matching, so trading from it is not interrupted.
Signing costs no gas: the venue’s executor submits the transaction. It is simulated first, so a withdrawal that
would revert is refused before anything is sent.
Signing the action
Sign anAction against the same Matching domain as an order, with:
| Field | Value |
|---|---|
subaccountId | the subaccount, deposited in Matching with you as its recorded owner |
nonce | any unused uint256; nonces are per owner and per module, so order nonces never collide with it |
module | the WithdrawalModule: 0x0a10AE2f5D2482cE1e43bC309D430B8861C2b5aB on Base |
data | abi.encode(address asset, uint256 amount) |
expiry | unix seconds, in the future and at most one hour ahead |
owner | your address; the tokens are sent here |
signer | the same as owner (session keys are not supported yet) |
asset is the wrapped asset your balance is held in, not the token it pays out:
| Balance | asset | Paid out as |
|---|---|---|
| USDC | 0x364058aFF6f36E01505fB2Cc870f8B6BD4835e84 | USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| cNGN | 0x9D806fD040a719D27a8E5E77dc5aE0ED1e089493 | cNGN 0x46C85152bFe9f96829aA94755D9f915F9B10EF5F |
amount is in the token’s native decimals — 6 for both, so 1 USDC is 1000000.
Send the action’s seven fields as strings (numbers in base 10, data as hex) with the signature:
{
"action": {
"subaccount_id": "19",
"nonce": "7328734720000000",
"module": "0x0a10AE2f5D2482cE1e43bC309D430B8861C2b5aB",
"data": "0x000000000000000000000000364058aff6f36e01505fb2cc870f8b6bd4835e8400000000000000000000000000000000000000000000000000000000001e82d7",
"expiry": "1789400600",
"owner": "0xYourAddress",
"signer": "0xYourAddress"
},
"signature": "0x…"
}
What is checked
In order, and every check fails closed — a signature or owner that cannot be read is refused, not waved through:- The request: module, subaccount (not 0), signer equals owner, expiry,
dataexactly two words, asset, amount above 0, signature shape. Refused with 400. - At most one withdrawal in progress per owner, and five a minute. Refused with 429.
- The signature authorizes the action. Refused with 401.
- Matching holds the subaccount (400) and records
owneras its owner (403). - The executor’s simulation. A withdrawal that would revert is refused with 422, and
revertnames the revert — for exampleWERC_CannotBeNegativefor more than the account holds.
The outcome
A 200 carries the transaction:accepted, tx_hash, receipt_status and block_number.
receipt_status: "timeout" means it was broadcast and had not confirmed when the venue answered — watch the hash
rather than treating it as failed.
A 502 means the venue could not confirm whether the withdrawal was submitted. Check your balance before signing
another: a second signed withdrawal is a second withdrawal. Resubmitting the same signed request is safe — its
nonce can only be spent once.Body
application/json
Was this page helpful?

